http-media 0.1.1 → 0.2.0
raw patch · 15 files changed
+440/−212 lines, 15 filesdep ~Cabaldep ~QuickCheck
Dependency ranges changed: Cabal, QuickCheck
Files
- LICENSE +1/−1
- dist/build/test-http-mediaStub/test-http-mediaStub-tmp/test-http-mediaStub.hs +1/−1
- http-media.cabal +30/−11
- src/Network/HTTP/Media.hs +158/−58
- src/Network/HTTP/Media/Accept.hs +54/−0
- src/Network/HTTP/Media/Match.hs +0/−39
- src/Network/HTTP/Media/MediaType.hs +0/−1
- src/Network/HTTP/Media/MediaType/Internal.hs +12/−17
- src/Network/HTTP/Media/Quality.hs +4/−2
- test/Network/HTTP/Media/Accept/Tests.hs +48/−0
- test/Network/HTTP/Media/Gen.hs +0/−1
- test/Network/HTTP/Media/Match/Tests.hs +0/−48
- test/Network/HTTP/Media/MediaType/Tests.hs +6/−6
- test/Network/HTTP/Media/Tests.hs +124/−25
- test/Tests.hs +2/−2
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2012 Timothy Jones+Copyright (c) 2012-2014 Timothy Jones Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in
dist/build/test-http-mediaStub/test-http-mediaStub-tmp/test-http-mediaStub.hs view
@@ -1,5 +1,5 @@ module Main ( main ) where-import Distribution.Simple.Test ( stubMain )+import Distribution.Simple.Test.LibV09 ( stubMain ) import Tests ( tests ) main :: IO () main = stubMain tests
http-media.cabal view
@@ -1,13 +1,13 @@ name: http-media-version: 0.1.1+version: 0.2.0 license: MIT license-file: LICENSE author: Timothy Jones-maintainer: Timothy Jones <git@zimothy.com>-homepage: http://github.com/zimothy/http-media-bug-reports: http://github.com/zimothy/http-media/issues+maintainer: Timothy Jones <git@zmthy.io>+homepage: http://github.com/zmthy/http-media+bug-reports: http://github.com/zmthy/http-media/issues category: Web-copyright: (c) 2013 Timothy Jones+copyright: (c) 2012-2014 Timothy Jones build-type: Simple cabal-version: >= 1.10 synopsis: Processing HTTP Content-Type and Accept headers@@ -18,7 +18,26 @@ matching against the other accept headers as well. It encodes MIME parameters into a 'MediaType' data, and allows the matching of the final value by comparing quality values from the client.+ .+ In the following example, the Accept header is parsed, and then matched+ against a list of server options to serve the appropriate media:+ .+ > getHeader >>= maybe send406Error sendResourceWith . mapAcceptMedia+ > [ ("text/html", asHtml)+ > , ("application/json", asJson)+ > ]+ .+ Similarly, the Content-Type header can be used to produce a parser for request+ bodies based on the given content type:+ .+ > getContentType >>= maybe send415Error readRequestBodyWith . mapContentMedia+ > [ ("application/json", parseJson)+ > , ("text/plain", parseText)+ > ]+ .+ The API is agnostic to your choice of server. + library hs-source-dirs: src @@ -30,7 +49,7 @@ exposed-modules: Network.HTTP.Media- Network.HTTP.Media.Match+ Network.HTTP.Media.Accept Network.HTTP.Media.MediaType other-modules: Network.HTTP.Media.MediaType.Internal@@ -53,9 +72,9 @@ test-module: Tests other-modules: Network.HTTP.Media+ Network.HTTP.Media.Accept+ Network.HTTP.Media.Accept.Tests Network.HTTP.Media.Gen- Network.HTTP.Media.Match- Network.HTTP.Media.Match.Tests Network.HTTP.Media.MediaType Network.HTTP.Media.MediaType.Gen Network.HTTP.Media.MediaType.Internal@@ -69,13 +88,13 @@ build-depends: base >= 4.6.0 && < 5.0, bytestring >= 0.10.0 && < 0.11,- Cabal >= 1.18.0 && < 1.19,+ Cabal >= 1.18.0 && < 1.21, cabal-test-quickcheck >= 0.1.0 && < 0.2, containers >= 0.5.0 && < 0.6,- QuickCheck >= 2.6 && < 2.7,+ QuickCheck >= 2.6 && < 2.9, utf8-string >= 0.3.7 && < 0.4 source-repository head type: git- location: git://github.com/zimothy/http-media.git+ location: git://github.com/zmthy/http-media.git
src/Network/HTTP/Media.hs view
@@ -11,81 +11,64 @@ , parameters , (/?) , (/.)- , Quality - -- * Parsing- , parseAccept- -- * Accept matching , matchAccept , mapAccept+ , mapAcceptMedia+ , mapAcceptBytes -- * Content matching , matchContent , mapContent+ , mapContentMedia - -- * Match- , Match (..)+ -- * Quality values+ , Quality+ , parseQuality+ , matchQuality+ , mapQuality++ -- * Accept+ , Accept (..) ) where ------------------------------------------------------------------------------ import qualified Data.ByteString as BS -------------------------------------------------------------------------------import Control.Applicative (pure, (<*>), (<|>))-import Control.Monad (guard)+import Control.Applicative (pure, (<$>), (<*>), (<|>))+import Control.Monad (guard, (>=>)) import Data.ByteString (ByteString, split) import Data.ByteString.UTF8 (toString) -------------------------------------------------------------------------------import Network.HTTP.Media.Match as Match+import Network.HTTP.Media.Accept as Accept import Network.HTTP.Media.MediaType as MediaType import Network.HTTP.Media.Quality import Network.HTTP.Media.Utils --------------------------------------------------------------------------------- | Parses a full Accept header into a list of quality-valued media types.-parseAccept :: ByteString -> Maybe [Quality MediaType]-parseAccept = (. split comma) . mapM $ \bs ->- let (accept, q) = BS.breakSubstring ";q=" $ BS.filter (/= space) bs- in (<*> parse accept) $ if BS.null q- then pure maxQuality else fmap (flip Quality) $ readQ- (toString $ BS.takeWhile (/= semi) $ BS.drop 3 q)--------------------------------------------------------------------------------- -- | Matches a list of server-side resource options against a quality-marked -- list of client-side preferences. A result of 'Nothing' means that nothing -- matched (which should indicate a 406 error). If two or more results arise -- with the same quality level and specificity, then the first one in the -- server list is chosen. ----- The use of the 'Match' type class allows the application of either+-- The use of the 'Accept' type class allows the application of either -- 'MediaType' for the standard Accept header or 'ByteString' for any other--- Accept header which can be marked with a quality value. The standard--- application of this function for 'MediaType' should be in conjunction with--- 'parseAccepts'.+-- Accept header which can be marked with a quality value. ----- > parseAccepts header >>= matchQuality resourceTypeOptions+-- > matchAccept ["text/html", "application/json"] <$> getHeader ----- For more information on the matching process see RFC 2616, section 14.+-- For more information on the matching process see RFC 2616, section 14.1-4. matchAccept- :: Match a- => [a] -- ^ The server-side options- -> [Quality a] -- ^ The client-side preferences+ :: Accept a+ => [a] -- ^ The server-side options+ -> ByteString -- ^ The client-side header value -> Maybe a-matchAccept server clientq = guard (hq /= 0) >> specific qs- where- merge (Quality c q) = map (`Quality` q) $ filter (`matches` c) server- matched = concatMap merge clientq- (hq, qs) = foldr qfold (0, []) matched- qfold (Quality v q) (mq, vs) = case compare q mq of- GT -> (q, [v])- EQ -> (mq, v : vs)- LT -> (mq, vs)- specific (a : ms) = Just $ foldl mostSpecific a ms- specific [] = Nothing+matchAccept = (parseQuality >=>) . matchQuality ------------------------------------------------------------------------------@@ -93,50 +76,167 @@ -- mapped to another value. Convenient for specifying how to translate the -- resource into each of its available formats. ----- > maybe render406Error renderResource $ parseAccepts header >>= mapQuality+-- > getHeader >>= maybe render406Error renderResource . mapAccept+-- > [ ("text" // "html", asHtml)+-- > , ("application" // "json", asJson)+-- > ]+mapAccept+ :: Accept a+ => [(a, b)] -- ^ The map of server-side preferences to values+ -> ByteString -- ^ The client-side header value+ -> Maybe b+mapAccept = (parseQuality >=>) . mapQuality+++------------------------------------------------------------------------------+-- | A specialisation of 'mapAccept' that only takes MediaType as its input,+-- to avoid ambiguous-type errors when using string literal overloading.+--+-- > getHeader >>= maybe render406Error renderResource . mapAcceptMedia -- > [ ("text/html", asHtml) -- > , ("application/json", asJson) -- > ]-mapAccept- :: Match a- => [(a, b)] -- ^ The map of server-side preferences to values- -> [Quality a] -- ^ The client-side preferences+mapAcceptMedia ::+ [(MediaType, b)] -- ^ The map of server-side preferences to values+ -> ByteString -- ^ The client-side header value -> Maybe b-mapAccept s c = matchAccept (map fst s) c >>= lookupMatches s+mapAcceptMedia = mapAccept ------------------------------------------------------------------------------+-- | A specialisation of 'mapAccept' that only takes ByteString as its input,+-- to avoid ambiguous-type errors when using string literal overloading.+--+-- > getHeader >>= maybe render406Error encodeResourceWith . mapAcceptBytes+-- > [ ("compress", compress)+-- > , ("gzip", gzip)+-- > ]+mapAcceptBytes ::+ [(ByteString, b)] -- ^ The map of server-side preferences to values+ -> ByteString -- ^ The client-side header value+ -> Maybe b+mapAcceptBytes = mapAccept+++------------------------------------------------------------------------------ -- | Matches a list of server-side parsing options against a the client-side -- content value. A result of 'Nothing' means that nothing matched (which -- should indicate a 415 error). ----- As with the Accept parsing, he use of the 'Match' type class allows the--- application of either 'MediaType' or 'ByteString'.+-- > matchContent ["application/json", "text/plain"] <$> getContentType+--+-- For more information on the matching process see RFC 2616, section 14.17. matchContent- :: Match a- => a -- ^ The client's request value- -> [a] -- ^ The server-side response options+ :: Accept a+ => [a] -- ^ The server-side response options+ -> ByteString -- ^ The client's request value -> Maybe a-matchContent client = foldl choose Nothing- where choose m server = m <|> (guard (matches client server) >> Just server)+matchContent options ctype = foldl choose Nothing options+ where+ choose m server = m <|> do+ parseAccept ctype >>= guard . (`matches` server)+ Just server ------------------------------------------------------------------------------ -- | The equivalent of 'matchContent' above, except the resulting choice is -- mapped to another value.+--+-- > getContentType >>= maybe send415Error readRequestBodyWith . mapContent+-- > [ ("application" // "json", parseJson)+-- > , ("text" // "plain", parseText)+-- > ] mapContent- :: Match a- => a -- ^ The client request's header value- -> [(a, b)] -- ^ The map of server-side responses+ :: Accept a+ => [(a, b)] -- ^ The map of server-side responses+ -> ByteString -- ^ The client request's header value -> Maybe b-mapContent c s = matchContent c (map fst s) >>= lookupMatches s+mapContent options ctype =+ matchContent (map fst options) ctype >>= lookupMatches options ------------------------------------------------------------------------------+-- | A specialisation of 'mapContent' that only takes MediaType as its input,+-- to avoid ambiguous-type errors when using string literal overloading.+--+-- > getContentType >>=+-- > maybe send415Error readRequestBodyWith . mapContentMedia+-- > [ ("application/json", parseJson)+-- > , ("text/plain", parseText)+-- > ]+mapContentMedia+ :: [(MediaType, b)] -- ^ The map of server-side responses+ -> ByteString -- ^ The client request's header value+ -> Maybe b+mapContentMedia = mapContent+++------------------------------------------------------------------------------+-- | Parses a full Accept header into a list of quality-valued media types.+parseQuality :: Accept a => ByteString -> Maybe [Quality a]+parseQuality = (. split comma) . mapM $ \bs ->+ let (accept, q) = BS.breakSubstring ";q=" $ BS.filter (/= space) bs+ in (<*> parseAccept accept) $ if BS.null q+ then pure maxQuality else flip Quality <$> readQ+ (toString $ BS.takeWhile (/= semi) $ BS.drop 3 q)+++------------------------------------------------------------------------------+-- | Matches a list of server-side resource options against a pre-parsed+-- quality-marked list of client-side preferences. A result of 'Nothing' means+-- that nothing matched (which should indicate a 406 error). If two or more+-- results arise with the same quality level and specificity, then the first+-- one in the server list is chosen.+--+-- The use of the 'Accept' type class allows the application of either+-- 'MediaType' for the standard Accept header or 'ByteString' for any other+-- Accept header which can be marked with a quality value.+--+-- > matchQuality ["text/html", "application/json"] <$> parseQuality header+--+-- For more information on the matching process see RFC 2616, section 14.1-4.+matchQuality+ :: Accept a+ => [a] -- ^ The server-side options+ -> [Quality a] -- ^ The pre-parsed client-side header value+ -> Maybe a+matchQuality options acceptq = do+ let merge (Quality c q) = map (`Quality` q) $ filter (`matches` c) options+ matched = concatMap merge acceptq+ (hq, qs) = foldr qfold (0, []) matched+ qfold (Quality v q) (mq, vs) = case compare q mq of+ GT -> (q, [v])+ EQ -> (mq, v : vs)+ LT -> (mq, vs)+ specific (a : ms) = Just $ foldl mostSpecific a ms+ specific [] = Nothing+ guard (hq /= 0)+ specific qs+++------------------------------------------------------------------------------+-- | The equivalent of 'matchQuality' above, except the resulting choice is+-- mapped to another value. Convenient for specifying how to translate the+-- resource into each of its available formats.+--+-- > parseQuality header >>= maybe render406Error renderResource . mapQuality+-- > [ ("text" // "html", asHtml)+-- > , ("application" // "json", asJson)+-- > ]+mapQuality+ :: Accept a+ => [(a, b)] -- ^ The map of server-side preferences to values+ -> [Quality a] -- ^ The client-side header value+ -> Maybe b+mapQuality options accept =+ matchQuality (map fst options) accept >>= lookupMatches options+++------------------------------------------------------------------------------ -- | The equivalent of 'lookupBy matches'.-lookupMatches :: Match a => [(a, b)] -> a -> Maybe b+lookupMatches :: Accept a => [(a, b)] -> a -> Maybe b lookupMatches ((k, v) : r) a- | Match.matches k a = Just v+ | Accept.matches k a = Just v | otherwise = lookupMatches r a lookupMatches [] _ = Nothing
+ src/Network/HTTP/Media/Accept.hs view
@@ -0,0 +1,54 @@+------------------------------------------------------------------------------+-- | Defines the 'Accept' type class, designed to unify types on the matching+-- functions in the Media module.+module Network.HTTP.Media.Accept+ ( Accept (..)+ , mostSpecific+ ) where++------------------------------------------------------------------------------+import Data.ByteString+import Data.ByteString.UTF8 (toString)+++------------------------------------------------------------------------------+-- | Defines methods for a type whose values can be matched against each+-- other in terms of an HTTP Accept-* header.+--+-- This allows functions to work on both the standard Accept header and+-- others such as Accept-Language that still may use quality values.+class Show a => Accept a where++ -- | Specifies how to parse an Accept-* header after quality has been+ -- handled.+ parseAccept :: ByteString -> Maybe a++ -- | Specifies how to show an Accept-* header. Defaults to the standard+ -- show method.+ --+ -- Mostly useful just for avoiding quotes when rendering 'ByteString's+ -- with accompanying quality.+ showAccept :: a -> String+ showAccept = show++ -- | Evaluates whether either the left argument matches the right one+ -- (order may be important).+ matches :: a -> a -> Bool++ -- | Evaluates whether the left argument is more specific than the right.+ moreSpecificThan :: a -> a -> Bool++instance Accept ByteString where+ parseAccept = Just+ showAccept = toString+ matches = (==)+ moreSpecificThan _ _ = False+++------------------------------------------------------------------------------+-- | Evaluates to whichever argument is more specific. Left biased.+mostSpecific :: Accept a => a -> a -> a+mostSpecific a b+ | b `moreSpecificThan` a = b+ | otherwise = a+
− src/Network/HTTP/Media/Match.hs
@@ -1,39 +0,0 @@---------------------------------------------------------------------------------- | Defines the 'Match' type class, designed to unify types on the--- matching functions in the Media module.-module Network.HTTP.Media.Match- ( Match (..)- , mostSpecific- ) where---------------------------------------------------------------------------------import Data.ByteString------------------------------------------------------------------------------------ | Defines methods for a type whose values can be matched against each--- other in terms of a HTTP media header.------ This allows functions to work on both the standard Accept header and--- others such as Accept-Language that still may use quality values.-class Match a where-- -- | Evaluates whether either the left argument matches the right one- -- (order may be important).- matches :: a -> a -> Bool-- -- | Evaluates whether the left argument is more specific than the right.- moreSpecificThan :: a -> a -> Bool--instance Match ByteString where- matches = (==)- moreSpecificThan _ _ = False------------------------------------------------------------------------------------ | Evaluates to whichever argument is more specific. Left biased.-mostSpecific :: Match a => a -> a -> a-mostSpecific a b- | b `moreSpecificThan` a = b- | otherwise = a-
src/Network/HTTP/Media/MediaType.hs view
@@ -7,7 +7,6 @@ , Parameters , (//) , (/:)- , parse , toByteString -- * Querying
src/Network/HTTP/Media/MediaType/Internal.hs view
@@ -4,7 +4,6 @@ ( MediaType (..) , Parameters , toByteString- , parse ) where ------------------------------------------------------------------------------@@ -22,7 +21,7 @@ import Data.Monoid ((<>)) -------------------------------------------------------------------------------import Network.HTTP.Media.Match (Match (..))+import Network.HTTP.Media.Accept (Accept (..)) import Network.HTTP.Media.Utils @@ -41,10 +40,19 @@ f k v = (++ ';' : toString k ++ '=' : toString v) instance IsString MediaType where- fromString str = flip fromMaybe (parse $ BS.fromString str) $+ fromString str = flip fromMaybe (parseAccept $ BS.fromString str) $ error $ "Invalid media type literal " ++ str -instance Match MediaType where+instance Accept MediaType where+ parseAccept bs = do+ let pieces = BS.split semi bs+ guard $ not (null pieces)+ let (m : ps) = pieces+ (a, b) = breakByte slash m+ guard $ BS.elem slash m && (a /= "*" || b == "*")+ return $ MediaType a b $+ foldr (uncurry Map.insert . breakByte equal) Map.empty ps+ matches a b | mainType b == "*" = params | subType b == "*" = mainType a == mainType b && params@@ -67,19 +75,6 @@ ------------------------------------------------------------------------------ -- | 'MediaType' parameters. type Parameters = Map ByteString ByteString------------------------------------------------------------------------------------ | Parses a media type header into a 'MediaType'.-parse :: ByteString -> Maybe MediaType-parse bs = do- let pieces = BS.split semi bs- guard $ not (null pieces)- let (m : ps) = pieces- (a, b) = breakByte slash m- guard $ BS.elem slash m && (a /= "*" || b == "*")- return $ MediaType a b $- foldr (uncurry Map.insert . breakByte equal) Map.empty ps ------------------------------------------------------------------------------
src/Network/HTTP/Media/Quality.hs view
@@ -11,6 +11,8 @@ import Data.Maybe (listToMaybe) import Data.Word (Word16) +------------------------------------------------------------------------------+import Network.HTTP.Media.Accept ------------------------------------------------------------------------------ -- | Attaches a quality value to data.@@ -19,8 +21,8 @@ , qualityValue :: Word16 } deriving (Eq) -instance Show a => Show (Quality a) where- show (Quality a q) = show a ++ ";q=" ++ showQ q+instance Accept a => Show (Quality a) where+ show (Quality a q) = showAccept a ++ ";q=" ++ showQ q ------------------------------------------------------------------------------
+ test/Network/HTTP/Media/Accept/Tests.hs view
@@ -0,0 +1,48 @@+------------------------------------------------------------------------------+module Network.HTTP.Media.Accept.Tests (tests) where++------------------------------------------------------------------------------+import Control.Monad (join, liftM, liftM2)+import Distribution.TestSuite.QuickCheck++------------------------------------------------------------------------------+import Network.HTTP.Media.Accept+import Network.HTTP.Media.Gen+++------------------------------------------------------------------------------+tests :: [Test]+tests =+ [ testMatches+ , testMoreSpecificThan+ , testMostSpecific+ ]+++------------------------------------------------------------------------------+testMatches :: Test+testMatches = testGroup "matches"+ [ testProperty "Does match" $ do+ string <- genByteString+ return $ matches string string+ , testProperty "Doesn't match" $ do+ string <- genByteString+ string' <- genDiffByteString string+ return . not $ matches string string'+ ]+++------------------------------------------------------------------------------+-- | Note that this test never actually generates any strings, as they are not+-- required for the 'moreSpecificThan' test.+testMoreSpecificThan :: Test+testMoreSpecificThan = testProperty "moreSpecificThan" $+ join (liftM2 ((not .) . moreSpecificThan)) genByteString+++------------------------------------------------------------------------------+testMostSpecific :: Test+testMostSpecific = testProperty "mostSpecific" $ do+ string <- genByteString+ liftM ((== string) . mostSpecific string) genByteString+
test/Network/HTTP/Media/Gen.hs view
@@ -26,4 +26,3 @@ bs' <- genByteString if bs == bs' then genDiffByteString bs else return bs' -
− test/Network/HTTP/Media/Match/Tests.hs
@@ -1,48 +0,0 @@--------------------------------------------------------------------------------module Network.HTTP.Media.Match.Tests (tests) where---------------------------------------------------------------------------------import Control.Monad (join, liftM, liftM2)-import Distribution.TestSuite.QuickCheck---------------------------------------------------------------------------------import Network.HTTP.Media.Gen-import Network.HTTP.Media.Match----------------------------------------------------------------------------------tests :: [Test]-tests =- [ testMatches- , testMoreSpecificThan- , testMostSpecific- ]----------------------------------------------------------------------------------testMatches :: Test-testMatches = testGroup "matches"- [ testProperty "Does match" $ do- string <- genByteString- return $ matches string string- , testProperty "Doesn't match" $ do- string <- genByteString- string' <- genDiffByteString string- return . not $ matches string string'- ]------------------------------------------------------------------------------------ | Note that this test never actually generates any strings, as they are not--- required for the 'moreSpecificThan' test.-testMoreSpecificThan :: Test-testMoreSpecificThan = testProperty "moreSpecificThan" $- join (liftM2 ((not .) . moreSpecificThan)) genByteString----------------------------------------------------------------------------------testMostSpecific :: Test-testMostSpecific = testProperty "mostSpecific" $ do- string <- genByteString- liftM ((== string) . mostSpecific string) genByteString-
test/Network/HTTP/Media/MediaType/Tests.hs view
@@ -13,7 +13,7 @@ import Distribution.TestSuite.QuickCheck -------------------------------------------------------------------------------import Network.HTTP.Media.Match+import Network.HTTP.Media.Accept import Network.HTTP.Media.MediaType ((/?), (/.)) import Network.HTTP.Media.MediaType.Internal import Network.HTTP.Media.MediaType.Gen@@ -30,7 +30,7 @@ , testMatches , testMoreSpecificThan , testMostSpecific- , testParse+ , testParseAccept ] @@ -52,7 +52,7 @@ testShow :: Test testShow = testProperty "show" $ do media <- genMediaType- return $ parse (BS.fromString $ show media) == Just media+ return $ parseAccept (BS.fromString $ show media) == Just media ------------------------------------------------------------------------------@@ -186,13 +186,13 @@ -------------------------------------------------------------------------------testParse :: Test-testParse = testProperty "parse" $ do+testParseAccept :: Test+testParseAccept = testProperty "parseAccept" $ do media <- genMediaType let main = mainType media sub = subType media params = parameters media- let (Just parsed) = parse $ main <> "/" <> sub <> mconcat+ let (Just parsed) = parseAccept $ main <> "/" <> sub <> mconcat (map (uncurry ((<>) . (<> "=") . (";" <>))) $ Map.toList params) return $ parsed == media
test/Network/HTTP/Media/Tests.hs view
@@ -3,10 +3,11 @@ ------------------------------------------------------------------------------ import Control.Monad (replicateM)+import Data.ByteString (ByteString) import Data.ByteString.UTF8 (fromString) import Data.List (intercalate) import Data.Map (empty)-import Data.Maybe (isNothing)+import Data.Maybe (isNothing, listToMaybe) import Data.Word (Word16) import Distribution.TestSuite.QuickCheck import Test.QuickCheck@@ -22,21 +23,25 @@ tests :: [Test] tests = [ testParse- , testMatch- , testMap+ , testMatchAccept+ , testMapAccept+ , testMatchContent+ , testMapContent+ , testMatchQuality+ , testMapQuality ] ------------------------------------------------------------------------------ testParse :: Test-testParse = testGroup "parseAccept"+testParse = testGroup "parseQuality" [ testProperty "Without quality" $ do media <- medias return $- parseAccept (group media) == Just (map maxQuality media)+ parseQuality (group media) == Just (map maxQuality media) , testProperty "With quality" $ do media <- medias >>= mapM (flip fmap (choose (0, 1000)) . Quality)- return $ parseAccept (group media) == Just media+ return $ parseQuality (group media) == Just media ] where medias = listOf1 genMediaType@@ -44,66 +49,160 @@ -------------------------------------------------------------------------------testMatch :: Test-testMatch = testGroup "match"+testMatchAccept :: Test+testMatchAccept = testMatch "Accept" matchAccept qToBS+++------------------------------------------------------------------------------+testMapAccept :: Test+testMapAccept = testMap "Accept" mapAccept qToBS+++------------------------------------------------------------------------------+testMatchContent :: Test+testMatchContent = testGroup "matchContent"+ [ testProperty "Most specific" $ do+ media <- genConcreteMediaType+ let client = toBS+ [ MediaType "*" "*" empty+ , media { subType = "*" }+ , media { parameters = empty }+ , media+ ]+ return $ matchAccept [media] client == Just media+ , testProperty "Nothing" $ do+ (server, client) <- genServerAndClient+ let client' = filter (not . flip any server . matches) client+ return . isNothing $ matchAccept server (toBS client')+ , testProperty "Left biased" $ do+ server <- genServer+ return $ matchAccept server (toBS server) == Just (head server)+ , testProperty "Against */*" $ do+ server <- genServer+ let stars = "*/*" :: ByteString+ return $ matchAccept server (toBS [stars]) == Just (head server)+ , testProperty "Against type/*" $ do+ server <- genServer+ let client = toBS [subStarOf $ head server]+ return $ matchAccept server client == Just (head server)+ ]+++------------------------------------------------------------------------------+testMapContent :: Test+testMapContent = testGroup "mapContent"+ [ testProperty "Matches" $ do+ server <- genServer+ let zipped = zip server server+ return $ mapAccept zipped (toBS server) == listToMaybe server+ , testProperty "Nothing" $ do+ server <- genServer+ client <- listOf1 $ genDiffMediaTypesWith genConcreteMediaType server+ let zipped = zip server $ repeat ()+ return . isNothing $ mapAccept zipped (toBS client)+ ]+++------------------------------------------------------------------------------+testMatchQuality :: Test+testMatchQuality = testMatch "Quality" matchQuality id+++------------------------------------------------------------------------------+testMapQuality :: Test+testMapQuality = testMap "Quality" mapQuality id+++------------------------------------------------------------------------------+testMatch+ :: String+ -> ([MediaType] -> a -> Maybe MediaType)+ -> ([Quality MediaType] -> a)+ -> Test+testMatch name match qToI = testGroup ("match" ++ name) [ testProperty "Highest quality" $ do server <- genServer qs <- replicateM (length server) $ choose (1, 1000) let client = zipWith Quality server qs qmax v q = if qualityValue q > qualityValue v then q else v- return $ matchAccept server client ==+ return $ match server (qToI client) == Just (qualityData $ foldr1 qmax client) , testProperty "Most specific" $ do media <- genConcreteMediaType- let client = map maxQuality+ let client = qToI $ map maxQuality [ MediaType "*" "*" empty , media { subType = "*" } , media { parameters = empty } , media ]- return $ matchAccept [media] client == Just media+ return $ match [media] client == Just media , testProperty "Nothing" $ do server <- genServer client <- listOf1 $ genDiffMediaTypesWith genConcreteMediaType server let client' = filter (not . flip any server . matches) client- return . isNothing $ matchAccept server (map maxQuality client')+ return . isNothing $ match server+ (qToI $ map maxQuality client') , testProperty "Never chooses q=0" $ do server <- genServer- return . isNothing $ matchAccept server (map minQuality server)+ return . isNothing $+ match server (qToI $ map minQuality server) , testProperty "Left biased" $ do server <- genServer- let client = map maxQuality server- return $ matchAccept server client == Just (head server)+ let client = qToI $ map maxQuality server+ return $ match server client == Just (head server) , testProperty "Against */*" $ do server <- genServer- return $ matchAccept server [maxQuality "*/*"] == Just (head server)+ let stars = "*/*" :: MediaType+ return $ match server (qToI [maxQuality stars]) ==+ Just (head server) , testProperty "Against type/*" $ do server <- genServer- let client = [maxQuality (subStarOf $ head server)]- return $ matchAccept server client == Just (head server)+ let client = qToI [maxQuality (subStarOf $ head server)]+ return $ match server client == Just (head server) ] -------------------------------------------------------------------------------testMap :: Test-testMap = testGroup "map"+testMap+ :: String+ -> ([(MediaType, MediaType)] -> a -> Maybe MediaType)+ -> ([Quality MediaType] -> a)+ -> Test+testMap name mapf qToI = testGroup ("map" ++ name) [ testProperty "Matches" $ do server <- genServer qs <- replicateM (length server) $ choose (1, 1000 :: Word16) let client = zipWith Quality server qs qmax q v = if qualityValue q >= qualityValue v then q else v zipped = zip server server- return $ mapAccept zipped client ==+ return $ mapf zipped (qToI client) == Just (qualityData $ foldr1 qmax client) , testProperty "Nothing" $ do- server <- genServer- client <- listOf1 $ genDiffMediaTypesWith genConcreteMediaType server- let zipped = zip server $ repeat ()- return . isNothing $ mapAccept zipped (map maxQuality client)+ (server, client) <- genServerAndClient+ let zipped = zip server $ repeat "*/*"+ return . isNothing $ mapf zipped (qToI $ map maxQuality client) ] ------------------------------------------------------------------------------ genServer :: Gen [MediaType] genServer = listOf1 genConcreteMediaType+++------------------------------------------------------------------------------+genServerAndClient :: Gen ([MediaType], [MediaType])+genServerAndClient = do+ server <- genServer+ client <- listOf1 $ genDiffMediaTypesWith genConcreteMediaType server+ return (server, client)+++------------------------------------------------------------------------------+toBS :: Accept a => [a] -> ByteString+toBS = fromString . intercalate "," . map showAccept+++------------------------------------------------------------------------------+qToBS :: Accept a => [Quality a] -> ByteString+qToBS = fromString . intercalate "," . map show
test/Tests.hs view
@@ -6,7 +6,7 @@ ------------------------------------------------------------------------------ import qualified Network.HTTP.Media.Tests as Media-import qualified Network.HTTP.Media.Match.Tests as Match+import qualified Network.HTTP.Media.Accept.Tests as Accept import qualified Network.HTTP.Media.MediaType.Tests as MediaType @@ -14,7 +14,7 @@ tests :: IO [Test] tests = return [ testGroup "MediaType" MediaType.tests- , testGroup "Match" Match.tests+ , testGroup "Accept" Accept.tests , testGroup "Media" Media.tests ]