---------------------------------------------------------
-- |
-- Module        : Network.Wai.Middleware.Select
-- Copyright     : Michael Snoyman
-- License       : BSD3
--
-- Maintainer    : Michael Snoyman <michael@snoyman.com>
-- Stability     : Unstable
-- Portability   : portable
--
-- Dynamically choose between Middlewares
--
-- It's useful when you want some 'Middleware's applied selectively.
--
-- Example: do not log health check calls:
--
-- > import Network.Wai
-- > import Network.Wai.Middleware.HealthCheckEndpoint
-- > import Network.Wai.Middleware.RequestLogger
-- >
-- > app' :: Application
-- > app' =
-- >   selectMiddleware (selectMiddlewareExceptRawPathInfo "/_healthz" logStdout)
-- >     $ healthCheck app
--
-- @since 3.1.10
--
---------------------------------------------------------
module Network.Wai.Middleware.Select
  ( -- * Middleware selection
    MiddlewareSelection (..),
    selectMiddleware,

    -- * Helpers
    selectMiddlewareOn,
    selectMiddlewareOnRawPathInfo,
    selectMiddlewareExceptRawPathInfo,
    passthroughMiddleware,
  )
where

import Control.Applicative ((<|>))
import Data.ByteString (ByteString)
import Data.Maybe (fromMaybe)
import Network.Wai

--------------------------------------------------
-- * Middleware selection
--------------------------------------------------

-- | Relevant Middleware for a given 'Request'.
newtype MiddlewareSelection = MiddlewareSelection
  { MiddlewareSelection -> Request -> Maybe Middleware
applySelectedMiddleware :: Request -> Maybe Middleware
  }

instance Semigroup MiddlewareSelection where
  MiddlewareSelection Request -> Maybe Middleware
f <> :: MiddlewareSelection -> MiddlewareSelection -> MiddlewareSelection
<> MiddlewareSelection Request -> Maybe Middleware
g =
    (Request -> Maybe Middleware) -> MiddlewareSelection
MiddlewareSelection forall a b. (a -> b) -> a -> b
$ \Request
req -> Request -> Maybe Middleware
f Request
req forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Request -> Maybe Middleware
g Request
req

instance Monoid MiddlewareSelection where
  mempty :: MiddlewareSelection
mempty = (Request -> Maybe Middleware) -> MiddlewareSelection
MiddlewareSelection forall a b. (a -> b) -> a -> b
$ forall a b. a -> b -> a
const forall a. Maybe a
Nothing

-- | Create the 'Middleware' dynamically applying 'MiddlewareSelection'.
selectMiddleware :: MiddlewareSelection -> Middleware
selectMiddleware :: MiddlewareSelection -> Middleware
selectMiddleware MiddlewareSelection
selection Application
app Request
request Response -> IO ResponseReceived
respond =
  Middleware
mw Application
app Request
request Response -> IO ResponseReceived
respond
  where
    mw :: Middleware
    mw :: Middleware
mw = forall a. a -> Maybe a -> a
fromMaybe Middleware
passthroughMiddleware (MiddlewareSelection -> Request -> Maybe Middleware
applySelectedMiddleware MiddlewareSelection
selection Request
request)

--------------------------------------------------
-- * Helpers
--------------------------------------------------

passthroughMiddleware :: Middleware
passthroughMiddleware :: Middleware
passthroughMiddleware = forall a. a -> a
id

-- | Use the 'Middleware' when the predicate holds.
selectMiddlewareOn :: (Request -> Bool) -> Middleware -> MiddlewareSelection
selectMiddlewareOn :: (Request -> Bool) -> Middleware -> MiddlewareSelection
selectMiddlewareOn Request -> Bool
doesApply Middleware
mw = (Request -> Maybe Middleware) -> MiddlewareSelection
MiddlewareSelection forall a b. (a -> b) -> a -> b
$ \Request
request ->
  if Request -> Bool
doesApply Request
request
    then forall a. a -> Maybe a
Just Middleware
mw
    else forall a. Maybe a
Nothing

-- | Use the `Middleware` for the given 'rawPathInfo'.
selectMiddlewareOnRawPathInfo :: ByteString -> Middleware -> MiddlewareSelection
selectMiddlewareOnRawPathInfo :: ByteString -> Middleware -> MiddlewareSelection
selectMiddlewareOnRawPathInfo ByteString
path = (Request -> Bool) -> Middleware -> MiddlewareSelection
selectMiddlewareOn ((forall a. Eq a => a -> a -> Bool
== ByteString
path) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> ByteString
rawPathInfo)

-- | Use the `Middleware` for all 'rawPathInfo' except then given one.
selectMiddlewareExceptRawPathInfo :: ByteString -> Middleware -> MiddlewareSelection
selectMiddlewareExceptRawPathInfo :: ByteString -> Middleware -> MiddlewareSelection
selectMiddlewareExceptRawPathInfo ByteString
path = (Request -> Bool) -> Middleware -> MiddlewareSelection
selectMiddlewareOn ((forall a. Eq a => a -> a -> Bool
/= ByteString
path) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> ByteString
rawPathInfo)