{-# LANGUAGE OverloadedStrings #-} module Web.Growler.Parsable where import qualified Data.ByteString.Char8 as BS import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as TL -- | Minimum implemention: 'parseParam' class Parsable a where -- | Take a 'ByteString' value and parse it as 'a', or fail with a message. parseParam :: BS.ByteString -> Either BS.ByteString a -- | Default implementation parses comma-delimited lists. -- -- > parseParamList t = mapM parseParam (BS.split ',' t) parseParamList :: BS.ByteString -> Either BS.ByteString [a] parseParamList t = mapM parseParam (BS.split ',' t) -- No point using 'read' for Text, ByteString, Char, and String. instance Parsable T.Text where parseParam = Right . T.decodeUtf8 instance Parsable TL.Text where parseParam = Right . TL.fromStrict . T.decodeUtf8 instance Parsable BS.ByteString where parseParam = Right -- | Overrides default 'parseParamList' to parse String. instance Parsable Char where parseParam t = case T.unpack $ T.decodeUtf8 t of [c] -> Right c _ -> Left "parseParam Char: no parse" parseParamList = Right . T.unpack . T.decodeUtf8 -- String -- | Checks if parameter is present and is null-valued, not a literal '()'. -- If the URI requested is: '/foo?bar=()&baz' then 'baz' will parse as (), where 'bar' will not. instance Parsable () where parseParam t = if BS.null t then Right () else Left "parseParam Unit: no parse" instance (Parsable a) => Parsable [a] where parseParam = parseParamList instance Parsable Bool where parseParam t = if t' == T.toCaseFold "true" then Right True else if t' == T.toCaseFold "false" then Right False else Left "parseParam Bool: no parse" where t' = T.toCaseFold $ T.decodeUtf8 t instance Parsable Double where parseParam = readEither instance Parsable Float where parseParam = readEither instance Parsable Int where parseParam = readEither instance Parsable Integer where parseParam = readEither -- | Useful for creating 'Parsable' instances for things that already implement 'Read'. Ex: -- -- > instance Parsable Int where parseParam = readEither readEither :: (Read a) => BS.ByteString -> Either BS.ByteString a readEither t = case [ x | (x, "") <- reads (T.unpack $ T.decodeUtf8 t) ] of [x] -> Right x [] -> Left "readEither: no parse" _ -> Left "readEither: ambiguous parse"