
Michael Snoyman
* "Request and response headers should have case-insensitive match for the Eq instance." I'm thinking that this is the correct approach to take, and would like input on it. (Thanks Gregory.
Here's our (dead simple) code for this:
------------------------------------------------------------------------
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.CIByteString
( CIByteString
, toCI
, unCI
) where
-- for IsString instance
import Data.ByteString.Char8 ()
import Data.ByteString (ByteString)
import Data.ByteString.Internal (c2w, w2c)
import qualified Data.ByteString as S
import Data.Char
import Data.String
-- | A case-insensitive newtype wrapper for ByteString
data CIByteString = CIByteString { unCI :: !ByteString
, _lowercased :: !ByteString }
toCI :: ByteString -> CIByteString
toCI s = CIByteString s t
where
t = lowercase s
instance Show CIByteString where
show (CIByteString s _) = show s
lowercase :: ByteString -> ByteString
lowercase = S.map (c2w . toLower . w2c)
instance Eq CIByteString where
(CIByteString _ a) == (CIByteString _ b) = a == b
(CIByteString _ a) /= (CIByteString _ b) = a /= b
instance Ord CIByteString where
(CIByteString _ a) <= (CIByteString _ b) = a <= b
instance IsString CIByteString where
fromString = toCI . fromString
------------------------------------------------------------------------
Note that we store the downcased version in the datatype, otherwise
Eq/Ord has to perform the downcase a zillion times, only to throw it
away afterwards. Note also that we ignore any encoding issues -- we can
get away w/ this because HTTP header names are constrained by the
protocol to be ASCII-only.
This datatype also has an "IsString" instance so you can use string
literals with "{-# LANGUAGE OverloadedStrings #-}" turned on.
G.
--
Gregory Collins