{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} -- | -- Module : Text.URI.Lens -- Copyright : © 2017–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov -- Stability : experimental -- Portability : portable -- -- Lenses for working with the 'URI' data type and its internals. module Text.URI.Lens ( uriScheme, uriAuthority, uriPath, isPathAbsolute, uriTrailingSlash, uriQuery, uriFragment, authUserInfo, authHost, authPort, uiUsername, uiPassword, _QueryFlag, _QueryParam, queryFlag, queryParam, unRText, ) where import Data.Foldable (find) import Data.Functor.Contravariant import qualified Data.List.NonEmpty as NE import Data.Maybe (isJust) import Data.Profunctor import Data.Text (Text) import Text.URI.Types ( Authority, QueryParam (..), RText, RTextLabel (..), URI, UserInfo, ) import qualified Text.URI.Types as URI #if !MIN_VERSION_base(4,18,0) import Control.Applicative (liftA2) #endif -- | 'URI' scheme lens. uriScheme :: Lens' URI (Maybe (RText 'Scheme)) uriScheme f s = (\x -> s {URI.uriScheme = x}) <$> f (URI.uriScheme s) -- | 'URI' authority lens. -- -- __Note__: before version /0.1.0.0/ this lens allowed to focus on @'Maybe' -- 'URI.Authority'@. uriAuthority :: Lens' URI (Either Bool URI.Authority) uriAuthority f s = (\x -> s {URI.uriAuthority = x}) <$> f (URI.uriAuthority s) -- | 'URI' path lens. uriPath :: Lens' URI [RText 'PathPiece] uriPath f s = (\x -> s {URI.uriPath = (ts,) <$> NE.nonEmpty x}) <$> f ps where ts = maybe False fst path ps = maybe [] (NE.toList . snd) path path = URI.uriPath s -- | A getter that can tell if path component of a 'URI' is absolute. -- -- @since 0.1.0.0 isPathAbsolute :: Getter URI Bool isPathAbsolute = to URI.isPathAbsolute -- | A 0-1 traversal allowing to view and manipulate trailing slash. -- -- @since 0.2.0.0 uriTrailingSlash :: Traversal' URI Bool uriTrailingSlash f s = (\x -> s {URI.uriPath = liftA2 (,) x ps}) <$> traverse f ts where ts = fst <$> path ps = snd <$> path path = URI.uriPath s -- | 'URI' query params lens. uriQuery :: Lens' URI [URI.QueryParam] uriQuery f s = (\x -> s {URI.uriQuery = x}) <$> f (URI.uriQuery s) -- | 'URI' fragment lens. uriFragment :: Lens' URI (Maybe (RText 'Fragment)) uriFragment f s = (\x -> s {URI.uriFragment = x}) <$> f (URI.uriFragment s) -- | 'Authority' user info lens. authUserInfo :: Lens' Authority (Maybe URI.UserInfo) authUserInfo f s = (\x -> s {URI.authUserInfo = x}) <$> f (URI.authUserInfo s) -- | 'Authority' host lens. authHost :: Lens' Authority (RText 'Host) authHost f s = (\x -> s {URI.authHost = x}) <$> f (URI.authHost s) -- | 'Authority' port lens. authPort :: Lens' Authority (Maybe Word) authPort f s = (\x -> s {URI.authPort = x}) <$> f (URI.authPort s) -- | 'UserInfo' username lens. uiUsername :: Lens' UserInfo (RText 'Username) uiUsername f s = (\x -> s {URI.uiUsername = x}) <$> f (URI.uiUsername s) -- | 'UserInfo' password lens. uiPassword :: Lens' UserInfo (Maybe (RText 'Password)) uiPassword f s = (\x -> s {URI.uiPassword = x}) <$> f (URI.uiPassword s) -- | 'QueryParam' prism for query flags. _QueryFlag :: Prism' URI.QueryParam (RText 'QueryKey) _QueryFlag = prism' QueryFlag $ \case QueryFlag x -> Just x _ -> Nothing -- | 'QueryParam' prism for query parameters. _QueryParam :: Prism' QueryParam (RText 'QueryKey, RText 'QueryValue) _QueryParam = prism' construct pick where construct (x, y) = QueryParam x y pick = \case QueryParam x y -> Just (x, y) _ -> Nothing -- | Check if the given query key is present in the collection of query -- parameters. queryFlag :: RText 'QueryKey -> Getter [URI.QueryParam] Bool queryFlag k = to (isJust . find g) where g (QueryFlag k') = k' == k g _ = False -- | Manipulate a query parameter by its key. Note that since there may be -- several query parameters with the same key this is a traversal that can -- return\/modify several items at once. queryParam :: RText 'QueryKey -> Traversal' [URI.QueryParam] (RText 'QueryValue) queryParam k f = traverse g where g p@(QueryParam k' v) = if k == k' then QueryParam k' <$> f v else pure p g p = pure p -- | A getter that can project 'Text' from refined text values. unRText :: Getter (RText l) Text unRText = to URI.unRText ---------------------------------------------------------------------------- -- Helpers type Lens' s a = forall f. (Functor f) => (a -> f a) -> s -> f s type Traversal' s a = forall f. (Applicative f) => (a -> f a) -> s -> f s type Getter s a = forall f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t) type Prism' s a = Prism s s a a -- | Build a 'Prism'. prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b prism bt seta = dimap seta (either pure (fmap bt)) . right' -- | Another way to build a 'Prism'. prism' :: (b -> s) -> (s -> Maybe a) -> Prism s s a b prism' bs sma = prism bs (\s -> maybe (Left s) Right (sma s)) -- | Lift a function into optic. to :: (Profunctor p, Contravariant f) => (s -> a) -> (p a (f a) -> p s (f s)) to f = dimap f (contramap f)