{-# LANGUAGE GeneralizedNewtypeDeriving, FlexibleInstances, CPP #-}
module Data.URLEncoded
(
URLEncoded
, filter
, URLShow(..)
, URLEncode(..)
, empty
, importString
, importList
, importURI
, (%=)
, (%=?)
, (%&)
, AddURLEncoded(..)
, null
, keys
, lookup
, lookupAll
, lookup1
, lookupDefault
, pairs
, (%!)
, addToURI
, export
)
where
import qualified Prelude
import Prelude hiding ( null, lookup, filter )
import Data.List.Split ( splitOn )
import Control.Monad ( liftM )
import Control.Arrow ( (>>>) )
#if MIN_VERSION_base(4,8,0)
import Control.Monad.Except ( MonadError )
#else
import Control.Monad.Error ( MonadError )
#endif
import Network.URI ( unEscapeString, escapeURIString, isUnreserved, URI(uriQuery) )
import Data.Monoid ( Monoid, mappend )
#if MIN_VERSION_base(4,9,0)
import Data.Semigroup ( Semigroup )
#endif
import Data.List ( intercalate )
import Data.Maybe ( fromMaybe )
newtype URLEncoded = URLEncoded { pairs :: [(String, String)] }
#if MIN_VERSION_base(4,9,0)
deriving (Semigroup, Monoid, Eq)
#else
deriving (Monoid, Eq)
#endif
class AddURLEncoded a where
(%?) :: URLEncode args => a -> args -> a
infixr 6 %?
instance AddURLEncoded [Char] where
str %? q = let (u, frag) = break (== '#') str
joiner = if last u == '?'
then ""
else if '?' `elem` u
then "&"
else "?"
in concat [u, joiner, export $ urlEncode q, frag]
instance AddURLEncoded URI where
u %? q = addToURI (urlEncode q) u
instance AddURLEncoded URLEncoded where
q1 %? q2 = q1 `mappend` urlEncode q2
(%&) :: (URLEncode q1, URLEncode q2) => q1 -> q2 -> URLEncoded
q1 %& q2 = urlEncode q1 `mappend` urlEncode q2
infixr 7 %&
null :: URLEncoded -> Bool
null = Prelude.null . pairs
empty :: URLEncoded
empty = URLEncoded []
importList :: [(String, String)] -> URLEncoded
importList = URLEncoded
keys :: URLEncoded -> [String]
keys = map fst . pairs
(%=) :: (URLShow a, URLShow b) => a -> b -> URLEncoded
k %= v = URLEncoded [(urlShow k, urlShow v)]
infixl 8 %=
class URLEncode a where
urlEncode :: a -> URLEncoded
instance (URLShow a, URLShow b) => URLEncode (a, b) where
urlEncode (x, y) = importList [(urlShow x, urlShow y)]
instance URLEncode a => URLEncode (Maybe a) where
urlEncode = maybe empty urlEncode
instance URLEncode URLEncoded where
urlEncode = id
class URLShow a where
urlShow :: a -> String
instance URLShow Char where
urlShow = return
instance URLShow URI where
urlShow = show
instance URLShow URLEncoded where
urlShow = export
instance URLShow [Char] where
urlShow = id
instance URLShow Int where
urlShow = show
instance URLShow Integer where
urlShow = show
instance URLShow Bool where
urlShow True = "true"
urlShow False = "false"
(%=?) :: (URLShow a, URLShow b) =>
a -> Maybe b -> URLEncoded
k %=? v = maybe empty (k %=) v
infixl 8 %=?
addToURI :: URLEncoded -> URI -> URI
addToURI q u =
let uq = uriQuery u
initial = if uq == "?"
then ""
else if Prelude.null (uriQuery u) then "?" else "&"
in u { uriQuery = uriQuery u ++ initial ++ export q }
export :: URLEncoded -> String
export q =
let esc = escapeURIString isUnreserved
encodePair (k, v) = esc k ++ "=" ++ esc v
in intercalate "&" $ map encodePair $ pairs q
instance Show URLEncoded where
showsPrec _ q = (export q ++)
importString :: MonadError e m => String -> m URLEncoded
importString "" = return empty
importString s = liftM importList $ mapM parsePair $ splitOn "&" s
where parsePair p =
case break (== '=') p of
(_, []) -> fail $ "Missing value in query string: " ++ show p
(k, '=':v) -> return ( unesc k
, unesc v
)
unknown -> error $ "impossible: " ++ show unknown
unesc = unEscapeString . intercalate "%20" . splitOn "+"
importURI :: MonadError e m => URI -> m URLEncoded
importURI u = case uriQuery u of
('?':s) -> importString s
[] -> return empty
q -> fail $ "Unexpected query for URI: " ++ show q
lookup1 :: (URLShow a, MonadError e m) => a -> URLEncoded -> m String
lookup1 k = lookup k >>> maybe missing return
where missing = fail $ "Key not found: " ++ urlShow k
lookup :: URLShow a => a -> URLEncoded -> Maybe String
lookup k = pairs >>> Prelude.lookup (urlShow k)
lookupDefault :: URLShow a => String -> a -> URLEncoded -> String
lookupDefault dflt k q = fromMaybe dflt $ q %! k
lookupAll :: URLShow a => a -> URLEncoded -> [String]
lookupAll k urlenc = [ v | (k', v) <- pairs urlenc, k' == urlShow k ]
filter :: ((String, String) -> Bool) -> URLEncoded -> URLEncoded
filter p = pairs >>> Prelude.filter p >>> URLEncoded
(%!) :: URLShow a => URLEncoded -> a -> Maybe String
(%!) = flip lookup
infixr 1 %!