packages feed

http-media 0.4.0 → 0.5.0

raw patch · 19 files changed

+303/−217 lines, 19 filesdep +case-insensitivedep −utf8-stringdep ~Cabaldep ~basedep ~bytestringsetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: case-insensitive

Dependencies removed: utf8-string

Dependency ranges changed: Cabal, base, bytestring, cabal-test-quickcheck, containers

API changes (from Hackage documentation)

+ Network.HTTP.Media: hasExtensionParameters :: Accept a => Proxy a -> Bool
+ Network.HTTP.Media.Accept: Proxy :: Proxy a
+ Network.HTTP.Media.Accept: data Proxy a
+ Network.HTTP.Media.Accept: hasExtensionParameters :: Accept a => Proxy a -> Bool
- Network.HTTP.Media: (/.) :: MediaType -> ByteString -> Maybe ByteString
+ Network.HTTP.Media: (/.) :: MediaType -> ByteString -> Maybe (CI ByteString)
- Network.HTTP.Media: class Show a => Accept a
+ Network.HTTP.Media: class Show a => Accept a where hasExtensionParameters _ = False
- Network.HTTP.Media: mainType :: MediaType -> ByteString
+ Network.HTTP.Media: mainType :: MediaType -> CI ByteString
- Network.HTTP.Media: subType :: MediaType -> ByteString
+ Network.HTTP.Media: subType :: MediaType -> CI ByteString
- Network.HTTP.Media: toParts :: Language -> [ByteString]
+ Network.HTTP.Media: toParts :: Language -> [CI ByteString]
- Network.HTTP.Media.Accept: class Show a => Accept a
+ Network.HTTP.Media.Accept: class Show a => Accept a where hasExtensionParameters _ = False
- Network.HTTP.Media.Language: toParts :: Language -> [ByteString]
+ Network.HTTP.Media.Language: toParts :: Language -> [CI ByteString]
- Network.HTTP.Media.MediaType: (/.) :: MediaType -> ByteString -> Maybe ByteString
+ Network.HTTP.Media.MediaType: (/.) :: MediaType -> ByteString -> Maybe (CI ByteString)
- Network.HTTP.Media.MediaType: mainType :: MediaType -> ByteString
+ Network.HTTP.Media.MediaType: mainType :: MediaType -> CI ByteString
- Network.HTTP.Media.MediaType: subType :: MediaType -> ByteString
+ Network.HTTP.Media.MediaType: subType :: MediaType -> CI ByteString
- Network.HTTP.Media.MediaType: type Parameters = Map ByteString ByteString
+ Network.HTTP.Media.MediaType: type Parameters = Map (CI ByteString) (CI ByteString)

Files

LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2012-2014 Timothy Jones+Copyright (c) 2012-2015 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@@ -17,4 +17,3 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.-
Setup.hs view
@@ -4,4 +4,3 @@  main :: IO () main = defaultMain-
http-media.cabal view
@@ -1,13 +1,13 @@ name:          http-media-version:       0.4.0+version:       0.5.0 license:       MIT license-file:  LICENSE author:        Timothy Jones maintainer:    Timothy Jones <git@zmthy.io>-homepage:      http://github.com/zmthy/http-media-bug-reports:   http://github.com/zmthy/http-media/issues+homepage:      https://github.com/zmthy/http-media+bug-reports:   https://github.com/zmthy/http-media/issues+copyright:     (c) 2012-2015 Timothy Jones category:      Web-copyright:     (c) 2012-2014 Timothy Jones build-type:    Simple cabal-version: >= 1.10 synopsis:      Processing HTTP Content-Type and Accept headers@@ -38,7 +38,6 @@   .   The API is agnostic to your choice of server. - library   hs-source-dirs: src @@ -60,10 +59,10 @@     Network.HTTP.Media.Quality     Network.HTTP.Media.Utils   build-depends:-    base        >= 4.6.0  && < 5.0,-    bytestring  >= 0.10.0 && < 0.11,-    containers  >= 0.5.0  && < 0.6,-    utf8-string >= 0.3.7  && < 0.4+    base             >= 4.6  && < 5.0,+    bytestring       >= 0.10 && < 0.11,+    case-insensitive >= 1.0  && < 1.3,+    containers       >= 0.5  && < 0.6  test-suite test-http-media   type:           detailed-0.9@@ -92,18 +91,17 @@     Network.HTTP.Media.Tests     Network.HTTP.Media.Utils -  ghc-options: -Wall -fhpc+  ghc-options: -Wall -Werror    build-depends:-    base                  >= 4.6.0  && < 5.0,-    bytestring            >= 0.10.0 && < 0.11,-    Cabal                 >= 1.18.0 && < 1.21,-    cabal-test-quickcheck >= 0.1.0  && < 0.2,-    containers            >= 0.5.0  && < 0.6,-    QuickCheck            >= 2.6    && < 2.9,-    utf8-string           >= 0.3.7  && < 0.4+    base                  >= 4.6  && < 5.0,+    bytestring            >= 0.10 && < 0.11,+    Cabal                 >= 1.19 && < 1.23,+    cabal-test-quickcheck >= 0.1  && < 0.2,+    case-insensitive      >= 1.0  && < 1.3,+    containers            >= 0.5  && < 0.6,+    QuickCheck            >= 2.6  && < 2.9  source-repository head   type:     git   location: git://github.com/zmthy/http-media.git-
src/Network/HTTP/Media.hs view
@@ -43,13 +43,13 @@     ) where  -------------------------------------------------------------------------------import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS  ------------------------------------------------------------------------------ import Control.Applicative  (pure, (<$>), (<*>), (<|>)) import Control.Monad        (guard, (>=>))-import Data.ByteString      (ByteString, split)-import Data.ByteString.UTF8 (toString)+import Data.ByteString      (ByteString)+import Data.Maybe           (fromMaybe)  ------------------------------------------------------------------------------ import Network.HTTP.Media.Accept       as Accept@@ -57,7 +57,7 @@ import Network.HTTP.Media.Language     as Language import Network.HTTP.Media.MediaType    as MediaType import Network.HTTP.Media.Quality-import Network.HTTP.Media.Utils+import Network.HTTP.Media.Utils        (trimBS)   ------------------------------------------------------------------------------@@ -218,13 +218,30 @@ ------------------------------------------------------------------------------ -- | 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)+parseQuality = parseQuality' Proxy +parseQuality' :: Accept a => Proxy a -> ByteString -> Maybe [Quality a]+parseQuality' p = (. map trimBS . BS.split ',') . mapM $ \ s ->+    let (accept, q) = fromMaybe (s, Nothing) $ if ext then findQ s else getQ s+    in maybe (pure maxQuality) (fmap (flip Quality) . readQ) q <*>+        parseAccept accept+  where+    ext = hasExtensionParameters p +    -- Split on ';', and check if a quality value is there. A value of Nothing+    -- indicates there was no parameter, whereas a value of Nothing in the+    -- pair indicates the parameter was not a quality value.+    getQ s = let (a, b) = trimBS <$> BS.breakEnd (== ';') s in+        if BS.null a then Nothing else Just (BS.init a,+            if BS.isPrefixOf "q=" b then Just (BS.drop 2 b) else Nothing)++    -- Trawl backwards through the string, ignoring extension parameters.+    findQ s = do+        let q = getQ s+        (a, m) <- q+        maybe (findQ a) (const q) m++ ------------------------------------------------------------------------------ -- | Matches a list of server-side resource options against a pre-parsed -- quality-marked list of client-side preferences. A result of 'Nothing' means@@ -283,4 +300,3 @@     | Accept.matches k a = Just v     | otherwise         = lookupMatches r a lookupMatches [] _ = Nothing-
src/Network/HTTP/Media/Accept.hs view
@@ -4,9 +4,13 @@ module Network.HTTP.Media.Accept     ( Accept (..)     , mostSpecific+    , Proxy (..)     ) where  ------------------------------------------------------------------------------+import qualified Data.CaseInsensitive as CI++------------------------------------------------------------------------------ import Data.ByteString (ByteString)  @@ -43,9 +47,15 @@     -- concept of specificity, this is the empty relation (always false).     moreSpecificThan :: a -> a -> Bool +    -- | Indicates whether extension parameters are permitted after the weight+    -- parameter when this type appears in an Accept header. Defaults to+    -- false.+    hasExtensionParameters :: Proxy a -> Bool+    hasExtensionParameters _ = False+ instance Accept ByteString where     parseAccept = Just-    matches = (==)+    matches a b = CI.mk a == CI.mk b     moreSpecificThan _ _ = False  @@ -56,3 +66,8 @@     | b `moreSpecificThan` a = b     | otherwise              = a ++------------------------------------------------------------------------------+-- | Serves the same purpose as the Proxy type in base, but redefined here in+-- a basic form for older versions of base that do not include it.+data Proxy a = Proxy
src/Network/HTTP/Media/Language.hs view
@@ -7,7 +7,8 @@     ) where  -------------------------------------------------------------------------------import Data.ByteString (ByteString)+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI)  ------------------------------------------------------------------------------ import Network.HTTP.Media.Language.Internal@@ -16,6 +17,5 @@ ------------------------------------------------------------------------------ -- | Converts 'Language' to a list of its language parts. The wildcard -- produces an empty list.-toParts :: Language -> [ByteString]+toParts :: Language -> [CI ByteString] toParts (Language l) = l-
src/Network/HTTP/Media/Language/Internal.hs view
@@ -6,21 +6,22 @@     ) where  -------------------------------------------------------------------------------import qualified Data.ByteString      as BS-import qualified Data.ByteString.UTF8 as BS hiding (length)+import qualified Data.ByteString.Char8 as BS+import qualified Data.CaseInsensitive  as CI  -------------------------------------------------------------------------------import Control.Monad   (guard)-import Data.ByteString (ByteString)-import Data.Functor    ((<$>))-import Data.List       (isPrefixOf)-import Data.Maybe      (fromMaybe)-import Data.String     (IsString (..))+import Control.Monad        (guard)+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI, original)+import Data.Char            (isAlpha)+import Data.Functor         ((<$>))+import Data.List            (isPrefixOf)+import Data.Maybe           (fromMaybe)+import Data.String          (IsString (..))  ------------------------------------------------------------------------------ import Network.HTTP.Media.Accept       (Accept (..)) import Network.HTTP.Media.RenderHeader (RenderHeader (..))-import Network.HTTP.Media.Utils        (hyphen, isAlpha)   ------------------------------------------------------------------------------@@ -30,30 +31,30 @@ -- Specifically: -- -- > language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )-newtype Language = Language [ByteString]+newtype Language = Language [CI ByteString]     deriving (Eq)  -- Note that internally, Language [] equates to *.  instance Show Language where-    show = BS.toString . renderHeader+    show = BS.unpack . renderHeader  instance IsString Language where     fromString "*" = Language []-    fromString str = flip fromMaybe (parseAccept $ BS.fromString str) $+    fromString str = flip fromMaybe (parseAccept $ BS.pack str) $         error $ "Invalid language literal " ++ str  instance Accept Language where     parseAccept "*" = Just $ Language []     parseAccept bs = do-        let pieces = BS.split hyphen bs+        let pieces = BS.split '-' bs         guard $ not (null pieces)         Language <$> mapM check pieces       where         check part = do             let len = BS.length part             guard $ len >= 1 && len <= 8 && BS.all isAlpha part-            return part+            return (CI.mk part)      -- Languages match if the right argument is a prefix of the left.     matches (Language a) (Language b)  = b `isPrefixOf` a@@ -65,5 +66,4 @@  instance RenderHeader Language where     renderHeader (Language []) = "*"-    renderHeader (Language l)  = BS.intercalate "-" l-+    renderHeader (Language l)  = BS.intercalate "-" (map original l)
src/Network/HTTP/Media/MediaType.hs view
@@ -18,13 +18,14 @@     ) where  -------------------------------------------------------------------------------import qualified Data.ByteString as BS-import qualified Data.Map        as Map+import qualified Data.ByteString.Char8 as BS+import qualified Data.CaseInsensitive  as CI+import qualified Data.Map              as Map  -------------------------------------------------------------------------------import Data.ByteString (ByteString)-import Data.Map        (empty, insert)-import Data.Word       (Word8)+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI)+import Data.Map             (empty, insert)  ------------------------------------------------------------------------------ import qualified Network.HTTP.Media.MediaType.Internal as Internal@@ -37,13 +38,13 @@  ------------------------------------------------------------------------------ -- | Retrieves the main type of a 'MediaType'.-mainType :: MediaType -> ByteString+mainType :: MediaType -> CI ByteString mainType = Internal.mainType   ------------------------------------------------------------------------------ -- | Retrieves the sub type of a 'MediaType'.-subType :: MediaType -> ByteString+subType :: MediaType -> CI ByteString subType = Internal.subType  @@ -70,20 +71,20 @@ ------------------------------------------------------------------------------ -- | Evaluates if a 'MediaType' has a parameter of the given name. (/?) :: MediaType -> ByteString -> Bool-(MediaType _ _ p) /? k = Map.member k p+(MediaType _ _ p) /? k = Map.member (CI.mk k) p   ------------------------------------------------------------------------------ -- | Retrieves a parameter from a 'MediaType'.-(/.) :: MediaType -> ByteString -> Maybe ByteString-(MediaType _ _ p) /. k = Map.lookup k p+(/.) :: MediaType -> ByteString -> Maybe (CI ByteString)+(MediaType _ _ p) /. k = Map.lookup (CI.mk k) p   ------------------------------------------------------------------------------ -- | Ensures that the 'ByteString' matches the ABNF for `reg-name` in RFC -- 4288.-ensureR :: ByteString -> ByteString-ensureR bs = if l == 0 || l > 127+ensureR :: ByteString -> CI ByteString+ensureR bs = CI.mk $ if l == 0 || l > 127     then error $ "Invalid length for " ++ show bs else ensure isValidChar bs   where l = BS.length bs @@ -92,13 +93,12 @@ -- | Ensures that the 'ByteString' does not contain invalid characters for -- a parameter value. RFC 4288 does not specify what characters are valid, so -- here we just disallow parameter and media type breakers, ',' and ';'.-ensureV :: ByteString -> ByteString-ensureV = ensure (`notElem` [44, 59])+ensureV :: ByteString -> CI ByteString+ensureV = CI.mk . ensure (`notElem` ",;")   ------------------------------------------------------------------------------ -- | Ensures the predicate matches for every character in the given string.-ensure :: (Word8 -> Bool) -> ByteString -> ByteString+ensure :: (Char -> Bool) -> ByteString -> ByteString ensure f bs = maybe     (error $ "Invalid character in " ++ show bs) (const bs) (BS.find f bs)-
src/Network/HTTP/Media/MediaType/Internal.hs view
@@ -6,48 +6,51 @@     ) where  -------------------------------------------------------------------------------import qualified Data.ByteString      as BS-import qualified Data.ByteString.UTF8 as BS-import qualified Data.Map             as Map+import qualified Data.ByteString.Char8 as BS+import qualified Data.CaseInsensitive  as CI+import qualified Data.Map              as Map  -------------------------------------------------------------------------------import Control.Monad   (guard)-import Data.ByteString (ByteString)-import Data.String     (IsString (..))-import Data.Map        (Map)-import Data.Maybe      (fromMaybe)-import Data.Monoid     ((<>))+import Control.Monad        (guard)+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI, original)+import Data.String          (IsString (..))+import Data.Map             (Map)+import Data.Maybe           (fromMaybe)+import Data.Monoid          ((<>))  ------------------------------------------------------------------------------ import Network.HTTP.Media.Accept       (Accept (..)) import Network.HTTP.Media.RenderHeader (RenderHeader (..))-import Network.HTTP.Media.Utils+import Network.HTTP.Media.Utils        (breakChar, trimBS)   ------------------------------------------------------------------------------ -- | An HTTP media type, consisting of the type, subtype, and parameters. data MediaType = MediaType-    { mainType   :: ByteString  -- ^ The main type of the MediaType-    , subType    :: ByteString  -- ^ The sub type of the MediaType-    , parameters :: Parameters  -- ^ The parameters of the MediaType+    { mainType   :: CI ByteString  -- ^ The main type of the MediaType+    , subType    :: CI ByteString  -- ^ The sub type of the MediaType+    , parameters :: Parameters     -- ^ The parameters of the MediaType     } deriving (Eq)  instance Show MediaType where-    show = BS.toString . renderHeader+    show = BS.unpack . renderHeader  instance IsString MediaType where-    fromString str = flip fromMaybe (parseAccept $ BS.fromString str) $+    fromString str = flip fromMaybe (parseAccept $ BS.pack str) $         error $ "Invalid media type literal " ++ str  instance Accept MediaType where     parseAccept bs = do-        let pieces = BS.split semi bs+        let pieces = map trimBS $ BS.split ';' 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+            (a, b)   = both CI.mk (breakChar '/' m)+        guard $ BS.elem '/' m && (a /= "*" || b == "*")+        return $ MediaType a b (foldr insert Map.empty ps)+      where+        both f (a, b) = (f a, f b)+        insert = uncurry Map.insert . both CI.mk . breakChar '='      matches a b         | mainType b == "*" = params@@ -67,13 +70,15 @@         subB = subType b == "*"         params = not (Map.null $ parameters a) && Map.null (parameters b) +    hasExtensionParameters _ = True+ instance RenderHeader MediaType where-    renderHeader (MediaType a b p) = Map.foldrWithKey f (a <> "/" <> b) p+    renderHeader (MediaType a b p) =+        Map.foldrWithKey f (original a <> "/" <> original b) p       where-        f k v = (<> ";" <> k <> "=" <> v)+        f k v = (<> ";" <> original k <> "=" <> original v)   ------------------------------------------------------------------------------ -- | 'MediaType' parameters.-type Parameters = Map ByteString ByteString-+type Parameters = Map (CI ByteString) (CI ByteString)
src/Network/HTTP/Media/Quality.hs view
@@ -4,23 +4,22 @@     ( Quality (..)     , maxQuality     , minQuality+    , showQ     , readQ     ) where  -------------------------------------------------------------------------------import qualified Data.ByteString      as BS-import qualified Data.ByteString.UTF8 as BS+import qualified Data.ByteString.Char8 as BS  ------------------------------------------------------------------------------ import Data.ByteString (ByteString)+import Data.Char       (isDigit) import Data.List       (dropWhileEnd)-import Data.Maybe      (listToMaybe) import Data.Monoid     ((<>)) import Data.Word       (Word16)  ------------------------------------------------------------------------------ import Network.HTTP.Media.RenderHeader (RenderHeader (..))-import Network.HTTP.Media.Utils        (zero)  ------------------------------------------------------------------------------ -- | Attaches a quality value to data.@@ -30,7 +29,7 @@     } deriving (Eq)  instance RenderHeader a => Show (Quality a) where-    show = BS.toString . renderHeader+    show = BS.unpack . renderHeader  instance RenderHeader h => RenderHeader (Quality h) where     renderHeader (Quality a q) = renderHeader a <> ";q=" <> showQ q@@ -53,23 +52,40 @@ showQ :: Word16 -> ByteString showQ 1000 = "1" showQ 0    = "0"-showQ q    = "0." <> BS.replicate (3 - length s) zero <> b+showQ q    = "0." <> BS.replicate (3 - length s) '0' <> b   where     s = show q-    b = BS.fromString (dropWhileEnd (== '0') s)+    b = BS.pack (dropWhileEnd (== '0') s)   ------------------------------------------------------------------------------ -- | Reads the standard quality representation into an integral value.-readQ :: String -> Maybe Word16-readQ "1" = Just 1000-readQ "0" = Just 0-readQ ('1' : '.' : t)-    | length t <= 3 && all (== '0') t = Just 1000-    | otherwise                       = Nothing-readQ ('0' : '.' : t)-    | length t <= 3 = fmap fst . listToMaybe . filter (null . snd) . reads $-        t ++ replicate (3 - length t) '0'-    | otherwise     = Nothing-readQ _   = Nothing+readQ :: ByteString -> Maybe Word16+readQ bs+    | BS.null bs = Nothing+    | h == '1'   = read1 t+    | h == '0'   = read0 t+    | otherwise  = Nothing+  where+    h = BS.head bs+    t = BS.tail bs +read1 :: ByteString -> Maybe Word16+read1 bs+    | BS.null bs || h == '.' && BS.length t < 4 && BS.all (== '0') t+                = Just 1000+    | otherwise = Nothing+  where+    h = BS.head bs+    t = BS.tail bs++read0 :: ByteString -> Maybe Word16+read0 bs+    | BS.null bs = Just 0+    | h == '.' && BS.length t < 4 && BS.all isDigit t+                = Just (toWord (t <> BS.replicate (3 - BS.length t) '0'))+    | otherwise = Nothing+  where+    h = BS.head bs+    t = BS.tail bs+    toWord = read . BS.unpack
src/Network/HTTP/Media/RenderHeader.hs view
@@ -26,4 +26,3 @@  instance RenderHeader h => RenderHeader [h] where     renderHeader = intercalate "," . map renderHeader-
src/Network/HTTP/Media/Utils.hs view
@@ -1,69 +1,43 @@ ----------------------------------------------------------------------------- -- | Common utilities. module Network.HTTP.Media.Utils-    ( breakByte+    ( breakChar     , trimBS -    , isAlpha     , validChars     , isValidChar--    , slash-    , semi-    , comma-    , space-    , equal-    , hyphen-    , zero     ) where  -------------------------------------------------------------------------------import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS  ------------------------------------------------------------------------------ import Data.ByteString (ByteString)-import Data.Word       (Word8)   --------------------------------------------------------------------------------- | Equivalent to 'Data.ByteString.breakByte', but leaves out the byte the--- string is broken on.-breakByte :: Word8 -> ByteString -> (ByteString, ByteString)-breakByte w = fmap BS.tail . BS.breakByte w+-- | Equivalent to 'Data.ByteString.break' (on equality against the given+-- character), but leaves out the byte that the string is broken on.+breakChar :: Char -> ByteString -> (ByteString, ByteString)+breakChar c = fmap BS.tail . BS.break (== c)   --------------------------------------------------------------------------------- | Trims space characters from both ends of a ByteString.+-- | Trims tab and space characters from both ends of a ByteString. trimBS :: ByteString -> ByteString-trimBS = BS.reverse . dropSpace . BS.reverse . dropSpace+trimBS = fst . BS.spanEnd isLWS . BS.dropWhile isLWS   where-    dropSpace = BS.dropWhile (== space)------------------------------------------------------------------------------------ | Evaluates whether the given character is in the standard ASCII alphabet.-isAlpha :: Word8 -> Bool-isAlpha c = c >= 65 && c <= 90 || c >= 97 && c <= 122+    isLWS c = c == ' ' || c == '\t'   ------------------------------------------------------------------------------ -- | List of the valid characters for a media-type `reg-name` as per RFC 4288.-validChars :: [Word8]-validChars =-    [33, 35, 36, 37, 43, 45, 46, 94, 95] ++ [48..57] ++ [65..90] ++ [97..122]+validChars :: [Char]+validChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "!#$&.+-^_"   ------------------------------------------------------------------------------ -- | Evaluates whether the given character is valid in a media type `reg-name` -- as per RFC 4288.-isValidChar :: Word8 -> Bool-isValidChar c = c >= 97 && c <= 122 || c >= 48 && c <= 57 ||-    c >= 65 && c <= 90 || c `elem` [33, 35, 36, 37, 43, 45, 46, 94, 95]------------------------------------------------------------------------------------ | 'ByteString' compatible characters.-slash, semi, comma, space, equal, hyphen, zero :: Word8-[slash, semi, comma, space, equal, hyphen, zero] =-    [47, 59, 44, 32, 61, 45, 48]-+isValidChar :: Char -> Bool+isValidChar = (`elem` validChars)
test/Network/HTTP/Media/Accept/Tests.hs view
@@ -45,4 +45,3 @@ testMostSpecific = testProperty "mostSpecific" $ do     string <- genByteString     liftM ((== string) . mostSpecific string) genByteString-
test/Network/HTTP/Media/Gen.hs view
@@ -2,42 +2,76 @@ -- | Contains definitions for generating 'ByteString's. module Network.HTTP.Media.Gen     ( genByteStringFrom+    , genCIByteStringFrom     , genByteString-    , genDiffByteStringWith+    , genCIByteString+    , genDiffWith     , genDiffByteString+    , genDiffCIByteString++    , padString     ) where  -------------------------------------------------------------------------------import Control.Applicative ((<$>))-import Data.ByteString     (ByteString, pack)-import Data.Word           (Word8)-import Test.QuickCheck.Gen (Gen, elements, listOf1)+import qualified Data.ByteString.Char8 as BS+import qualified Data.CaseInsensitive  as CI +------------------------------------------------------------------------------+import Control.Applicative  ((<$>))+import Control.Monad        (liftM2, join)+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI, original)+import Data.Monoid          ((<>))+import Test.QuickCheck.Gen  (Gen, elements, listOf, listOf1) + ------------------------------------------------------------------------------ -- | Produces a non-empty ByteString of random characters from the given set.-genByteStringFrom :: [Word8] -> Gen ByteString-genByteStringFrom validChars = pack <$> listOf1 (elements validChars)+genByteStringFrom :: [Char] -> Gen ByteString+genByteStringFrom = fmap BS.pack . listOf1 . elements   ------------------------------------------------------------------------------+genCIByteStringFrom :: [Char] -> Gen (CI ByteString)+genCIByteStringFrom = fmap CI.mk . genByteStringFrom+++------------------------------------------------------------------------------ -- | Produces a non-empty ByteString of random alphanumeric characters. genByteString :: Gen ByteString-genByteString = genByteStringFrom $ [48..57] ++ [65..90] ++ [97..122]+genByteString = genByteStringFrom (['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'])   ------------------------------------------------------------------------------+genCIByteString :: Gen (CI ByteString)+genCIByteString = fmap CI.mk genByteString+++------------------------------------------------------------------------------ -- | Produces a non-empty ByteString different to the given one using the -- given generator.-genDiffByteStringWith :: Gen ByteString -> ByteString -> Gen ByteString-genDiffByteStringWith gen bs = do-    bs' <- gen-    if bs == bs' then genDiffByteStringWith gen bs else return bs'+genDiffWith :: Eq a => Gen a -> a -> Gen a+genDiffWith gen a = do+    b <- gen+    if a == b then genDiffWith gen a else return b   --------------------------------------------------------------------------------- | Produces a non-empty ByteString of random alphanumeric characters--- different to the given one.+-- | Produces a non-empty ByteString of random alphanumeric characters that+-- is case-insensitively different to the given one. genDiffByteString :: ByteString -> Gen ByteString-genDiffByteString = genDiffByteStringWith genByteString+genDiffByteString = fmap original . genDiffCIByteString . CI.mk ++------------------------------------------------------------------------------+-- | Produces a non-empty case-insensitive ByteString of random alphanumeric+-- characters that is different to the given one.+genDiffCIByteString :: CI ByteString -> Gen (CI ByteString)+genDiffCIByteString = genDiffWith genCIByteString+++------------------------------------------------------------------------------+-- | Pad a 'ByteString' with a random amount of tab and space characters.+padString :: ByteString -> Gen ByteString+padString c = join (liftM2 pad) (BS.pack <$> listOf (elements " \t"))+  where pad a b = a <> c <> b
test/Network/HTTP/Media/Language/Gen.hs view
@@ -18,8 +18,9 @@     ) where  -------------------------------------------------------------------------------import Control.Applicative ((<$>))-import Data.ByteString     (ByteString)+import Control.Applicative  ((<$>))+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI) import Test.QuickCheck.Gen  ------------------------------------------------------------------------------@@ -38,36 +39,34 @@ ------------------------------------------------------------------------------ -- | Generates any kind of Language. genLanguage :: Gen Language-genLanguage = Language <$> listOf genByteString+genLanguage = Language <$> listOf genCIByteString   ------------------------------------------------------------------------------ -- | Generates a Language that does not match everything. genConcreteLanguage :: Gen Language-genConcreteLanguage = Language <$> listOf1 genByteString+genConcreteLanguage = Language <$> listOf1 genCIByteString   ------------------------------------------------------------------------------ -- | Generates a different Language to the given one. genDiffLanguage :: Language -> Gen Language genDiffLanguage (Language []) = genConcreteLanguage-genDiffLanguage lang           = do-    lang' <- genLanguage-    if lang == lang' then genDiffLanguage lang else return lang'+genDiffLanguage l             = Gen.genDiffWith genLanguage l   ------------------------------------------------------------------------------ -- | Generate a Language that has the given language as a prefix. genMatchingLanguage :: Language -> Gen Language genMatchingLanguage (Language pre) =-    (Language . (pre ++)) <$> listOf genByteString+    (Language . (pre ++)) <$> listOf genCIByteString   ------------------------------------------------------------------------------ -- | Generate a Language that has the given language as a proper prefix. genDiffMatchingLanguage :: Language -> Gen Language genDiffMatchingLanguage (Language pre) =-    (Language . (pre ++)) <$> listOf1 genByteString+    (Language . (pre ++)) <$> listOf1 genCIByteString   ------------------------------------------------------------------------------@@ -75,7 +74,7 @@ genNonMatchingLanguage :: Language -> Gen Language genNonMatchingLanguage (Language [])        = genConcreteLanguage genNonMatchingLanguage (Language (pre : _)) = do-    pre' <- genDiffByteString pre+    pre' <- genDiffCIByteString pre     genMatchingLanguage $ Language [pre']  @@ -109,11 +108,11 @@   -------------------------------------------------------------------------------genByteString :: Gen ByteString-genByteString = resize 8 $ Gen.genByteStringFrom ([65..90] ++ [97..122])+genCIByteString :: Gen (CI ByteString)+genCIByteString =+    resize 8 $ Gen.genCIByteStringFrom (['a'..'z'] ++ ['A'..'Z'])   -------------------------------------------------------------------------------genDiffByteString :: ByteString -> Gen ByteString-genDiffByteString = Gen.genDiffByteStringWith genByteString-+genDiffCIByteString :: CI ByteString -> Gen (CI ByteString)+genDiffCIByteString = Gen.genDiffWith genCIByteString
test/Network/HTTP/Media/Language/Tests.hs view
@@ -2,7 +2,7 @@ module Network.HTTP.Media.Language.Tests (tests) where  -------------------------------------------------------------------------------import qualified Data.ByteString.UTF8 as BS+import qualified Data.ByteString.Char8 as BS  ------------------------------------------------------------------------------ import Control.Applicative               ((<$>))@@ -47,7 +47,7 @@ testShow :: Test testShow = testProperty "show" $ do     lang <- genLanguage-    return $ parseAccept (BS.fromString $ show lang) == Just lang+    return $ parseAccept (BS.pack $ show lang) == Just lang   ------------------------------------------------------------------------------@@ -112,4 +112,3 @@ testParseAccept = testProperty "parseAccept" $ do     lang <- genLanguage     return $ parseAccept (renderHeader lang) == Just lang-
test/Network/HTTP/Media/MediaType/Gen.hs view
@@ -21,15 +21,22 @@     , genParameters     , genMaybeParameters     , genDiffParameters++    -- * Rendering Parameters+    , renderParameters     ) where  ------------------------------------------------------------------------------ import qualified Data.Map as Map  -------------------------------------------------------------------------------import Control.Monad       (liftM, liftM2)-import Data.ByteString     (ByteString)-import Data.Map            (fromList)+import Control.Applicative  ((<$>))+import Control.Monad        (liftM, liftM2)+import Data.ByteString      (ByteString)+import Data.CaseInsensitive (CI, original)+import Data.Foldable        (foldlM)+import Data.Map             (fromList)+import Data.Monoid          ((<>)) import Test.QuickCheck.Gen  ------------------------------------------------------------------------------@@ -39,7 +46,7 @@  ------------------------------------------------------------------------------ -- | Parameter entry for testing.-type ParamEntry = (ByteString, ByteString)+type ParamEntry = (CI ByteString, CI ByteString)   ------------------------------------------------------------------------------@@ -58,7 +65,7 @@ -- | Generates a MediaType with just a concrete main type. genSubStar :: Gen MediaType genSubStar = do-    main <- genByteString+    main <- genCIByteString     return $ MediaType main "*" Map.empty  @@ -78,8 +85,8 @@ -- | Generates a concrete MediaType which may have parameters. genConcreteMediaType :: Gen MediaType genConcreteMediaType = do-    main <- genByteString-    sub  <- genByteString+    main <- genCIByteString+    sub  <- genCIByteString     params <- oneof [return Map.empty, genParameters]     return $ MediaType main sub params @@ -88,8 +95,8 @@ -- | Generates a concrete MediaType with no parameters. genWithoutParams :: Gen MediaType genWithoutParams = do-    main <- genByteString-    sub  <- genByteString+    main <- genCIByteString+    sub  <- genCIByteString     return $ MediaType main sub Map.empty  @@ -97,8 +104,8 @@ -- | Generates a MediaType with at least one parameter. genWithParams :: Gen MediaType genWithParams = do-    main   <- genByteString-    sub    <- genByteString+    main   <- genCIByteString+    sub    <- genCIByteString     params <- genParameters     return $ MediaType main sub params @@ -143,7 +150,7 @@ -- | Reuse for 'mayParams' and 'someParams'. mkGenParams :: (Gen ParamEntry -> Gen [ParamEntry]) -> Gen Parameters mkGenParams = liftM fromList .-    ($ liftM2 (,) (genDiffByteString "q") genByteString)+    ($ liftM2 (,) (genDiffCIByteString "q") genCIByteString)   ------------------------------------------------------------------------------@@ -168,3 +175,14 @@         then genDiffParameters params         else return params' ++------------------------------------------------------------------------------+-- | Render parameters with a generated amount of whitespace between the+-- semicolons. Note that there is a leading semicolon in front of the+-- parameters, as it is expected that this will always be attached to+-- a preceding 'MediaType' rendering.+renderParameters :: Parameters -> Gen ByteString+renderParameters params = foldlM pad "" (Map.toList params)+  where+    pad s (k, v) =+        (s <>) . (<> original k <> "=" <> original v) <$> padString ";"
test/Network/HTTP/Media/MediaType/Tests.hs view
@@ -2,14 +2,15 @@ module Network.HTTP.Media.MediaType.Tests (tests) where  -------------------------------------------------------------------------------import qualified Data.ByteString.UTF8 as BS-import qualified Data.Map             as Map+import qualified Data.ByteString.Char8 as BS+import qualified Data.Map              as Map  ------------------------------------------------------------------------------ import Control.Monad                     (join, liftM)+import Data.CaseInsensitive              (foldedCase) import Data.String                       (fromString) import Data.Maybe                        (isNothing)-import Data.Monoid                       ((<>), mconcat)+import Data.Monoid                       ((<>)) import Distribution.TestSuite.QuickCheck  ------------------------------------------------------------------------------@@ -53,7 +54,7 @@ testShow :: Test testShow = testProperty "show" $ do     media <- genMediaType-    return $ parseAccept (BS.fromString $ show media) == Just media+    return $ parseAccept (BS.pack $ show media) == Just media   ------------------------------------------------------------------------------@@ -68,10 +69,10 @@ testHas = testGroup "(/?)"     [ testProperty "True for property it has" $ do         media <- genWithParams-        return $ all (media /?) (Map.keys $ parameters media)+        return $ all ((media /?) . foldedCase) (Map.keys $ parameters media)     , testProperty "False for property it doesn't have" $ do         media <- genWithParams-        return $ all (not . (stripParams media /?))+        return $ all (not . (stripParams media /?) . foldedCase)             (Map.keys $ parameters media)     ] @@ -81,11 +82,11 @@ testGet = testGroup "(/.)"     [ testProperty "Retrieves property it has" $ do         media  <- genWithParams-        let is n v = (&& media /. n == Just v)+        let is n v = (&& media /. foldedCase n == Just v)         return $ Map.foldrWithKey is True $ parameters media     , testProperty "Nothing for property it doesn't have" $ do         media <- genWithParams-        let is n _ = (&& isNothing (stripParams media /. n))+        let is n _ = (&& isNothing (stripParams media /. foldedCase n))         return $ Map.foldrWithKey is True $ parameters media     ] @@ -98,12 +99,12 @@         return $ matches media media     , testProperty "Same sub but different main don't match" $ do         media <- genMaybeSubStar-        main  <- genDiffByteString $ mainType media+        main  <- genDiffCIByteString $ mainType media         return $ not (matches media media { mainType = main }) &&             not (matches media { mainType = main } media)     , testProperty "Same main but different sub don't match" $ do         media <- genConcreteMediaType-        sub   <- genDiffByteString $ subType media+        sub   <- genDiffCIByteString $ subType media         return . not $ matches media media { subType = sub } ||             matches media { subType = sub } media     , testProperty "Different parameters don't match" $@@ -192,9 +193,8 @@     media <- genMediaType     let main   = mainType media         sub    = subType media-        params = parameters media-        parsed = parseAccept $ main <> "/" <> sub <> mconcat-            (map (uncurry ((<>) . (<> "=") . (";" <>))) $ Map.toList params)+    params <- renderParameters (parameters media)+    let parsed = parseAccept $ foldedCase (main <> "/" <> sub) <> params     return $ parsed == Just media  @@ -202,4 +202,3 @@ -- | Like 'join', but applies the given function to the first argument. dotJoin :: (a -> a -> b) -> (a -> a) -> a -> b dotJoin f g a = f (g a) a-
test/Network/HTTP/Media/Tests.hs view
@@ -2,21 +2,24 @@ module Network.HTTP.Media.Tests (tests) where  -------------------------------------------------------------------------------import Control.Monad                     (replicateM)+import Control.Applicative               ((<$>), (<*>))+import Control.Monad                     ((>=>), replicateM) import Data.ByteString                   (ByteString)+import Data.Foldable                     (foldlM) import Data.Map                          (empty) import Data.Maybe                        (isNothing, listToMaybe)+import Data.Monoid                       ((<>)) import Data.Word                         (Word16) import Distribution.TestSuite.QuickCheck import Test.QuickCheck  ------------------------------------------------------------------------------ import Network.HTTP.Media                    hiding (parameters, subType)+import Network.HTTP.Media.Gen                (padString) import Network.HTTP.Media.MediaType.Gen import Network.HTTP.Media.MediaType.Internal import Network.HTTP.Media.Quality - ------------------------------------------------------------------------------ tests :: [Test] tests =@@ -34,15 +37,30 @@ testParse :: Test testParse = testGroup "parseQuality"     [ testProperty "Without quality" $ do-        media <- medias-        return $-            parseQuality (renderHeader media) == Just (map maxQuality media)+        media    <- medias+        rendered <- padConcat (return . renderHeader) media+        return $ parseQuality rendered == Just (map maxQuality media)     , testProperty "With quality" $ do-        media <- medias >>= mapM (flip fmap (choose (0, 1000)) . Quality)-        return $ parseQuality (renderHeader media) == Just media+        media    <- qualities+        rendered <- padConcat padQuality media+        return $ parseQuality rendered == Just media+    , testProperty "With extensions" $ do+        media    <- qualities+        rendered <- padConcat (padQuality >=> padExtensions) media+        return $ parseQuality rendered == Just media     ]   where     medias = listOf1 genMediaType+    qualities = medias >>= mapM (flip fmap (choose (0, 1000)) . Quality)+    padConcat f l = flip (foldlM (padComma f)) (tail l) =<< f (head l)+    padComma f a b = pad a <$> padString "," <*> f b+    padQuality qMedia = do+        semi <- padString ";"+        let d = renderHeader (qualityData qMedia)+            v = showQ (qualityValue qMedia)+        return $ d <> semi <> "q=" <> v+    padExtensions s = genParameters >>= fmap (s <>) . renderParameters+    pad a s b = a <> s <> b   ------------------------------------------------------------------------------@@ -194,4 +212,3 @@     server <- genServer     client <- listOf1 $ genDiffMediaTypesWith genConcreteMediaType server     return (server, client)-