web-view-0.4.0: src/Web/View/Types/Url.hs
module Web.View.Types.Url where
import Control.Applicative ((<|>))
import Data.Maybe (fromMaybe)
import Data.String (IsString (..))
import Data.Text (Text, pack)
import Data.Text qualified as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import Debug.Trace
import Effectful
import Effectful.State.Static.Local
import Network.HTTP.Types (Query, parseQuery, renderQuery)
type Segment = Text
pathUrl :: [Segment] -> Url
pathUrl p = Url "" "" p []
cleanSegment :: Segment -> Segment
cleanSegment = T.dropWhileEnd (== '/') . T.dropWhile (== '/')
pathSegments :: Text -> [Segment]
pathSegments p = filter (not . T.null) $ T.splitOn "/" $ T.dropWhile (== '/') p
-- Problem: if scheme and domain exist, it MUST be an absolute url
data Url = Url
{ scheme :: Text
, domain :: Text
, path :: [Segment]
, query :: Query
}
deriving (Show, Eq)
instance IsString Url where
fromString = url . pack
url :: Text -> Url
url t = runPureEff $ evalState (T.toLower t) $ do
s <- scheme
d <- domain s
ps <- paths
q <- query
pure $ Url{scheme = s, domain = d, path = ps, query = q}
where
parse :: (State Text :> es) => (Char -> Bool) -> Eff es Text
parse b = do
inp <- get
let match = T.takeWhile b inp
rest = T.dropWhile b inp
put rest
pure match
string :: (State Text :> es) => Text -> Eff es (Maybe Text)
string pre = do
inp <- get
case T.stripPrefix pre inp of
Nothing -> pure Nothing
Just rest -> do
traceM $ show ("Prefix" :: String, rest, pre, inp)
put rest
pure (Just pre)
-- it's either scheme AND domain, or relative path
scheme = do
http <- string "http://"
https <- string "https://"
pure $ fromMaybe "" $ http <|> https
domain "" = pure ""
domain _ = parse (not . isDomainSep)
pathText :: (State Text :> es) => Eff es Text
pathText = parse (not . isQuerySep)
paths :: (State Text :> es) => Eff es [Segment]
paths = do
p <- pathText
pure $ pathSegments p
query :: (State Text :> es) => Eff es Query
query = do
q <- parse (/= '\n')
pure $ parseQuery $ encodeUtf8 q
isDomainSep '/' = True
isDomainSep _ = False
isQuerySep '?' = True
isQuerySep _ = False
renderUrl :: Url -> Text
renderUrl u = u.scheme <> u.domain <> paths u.path <> decodeUtf8 (renderQuery True u.query)
where
paths :: [Segment] -> Text
paths ss = "/" <> T.intercalate "/" (map cleanSegment ss)