packages feed

waiz-0.0.2.0: src/Network/Wai/Optics/Response.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wall #-}

-- | Classy optics and constructor prisms for 'Response'.
module Network.Wai.Optics.Response (
  -- * Classy optics
  GetResponse (..),
  HasResponse (..),
  ReviewResponse (..),
  AsResponse (..),

  -- * Constructor prisms

  -- ** ResponseFile
  ReviewResponseFile (..),
  AsResponseFile (..),

  -- ** ResponseBuilder
  ReviewResponseBuilder (..),
  AsResponseBuilder (..),

  -- ** ResponseStream
  ReviewResponseStream (..),
  AsResponseStream (..),

  -- ** ResponseRaw
  ReviewResponseRaw (..),
  AsResponseRaw (..),

  -- * Setters
  applicationResponse,
) where

import Control.Category (id, (.))
import Control.Lens (Getter, Lens', Prism', Review, Setter', lens, prism', review, sets, to, unto, view)
import Data.ByteString (ByteString)
import Data.ByteString.Builder (Builder)
import Data.Maybe (Maybe (..))
import Network.HTTP.Types (ResponseHeaders, Status)
import Network.Wai (Application, Response, StreamingBody)
import qualified Network.Wai as Wai
import Network.Wai.Internal (
  FilePart,
  Response (..),
 )
import Prelude (FilePath, IO, const, flip, uncurry)

-- | Type class for values that can be viewed as a 'Response'.
class GetResponse s where
  -- | A 'Getter' to obtain a 'Response'.
  getResponse :: Getter s Response
  -- | A 'Getter' on the 'Status'.
  getResponseStatus :: Getter s Status
  getResponseStatus = getResponse . getResponseStatus
  {-# INLINE getResponseStatus #-}
  -- | A 'Getter' on the 'ResponseHeaders'.
  getResponseHeaders :: Getter s ResponseHeaders
  getResponseHeaders = getResponse . getResponseHeaders
  {-# INLINE getResponseHeaders #-}

instance GetResponse Response where
  getResponse = id
  {-# INLINE getResponse #-}
  getResponseStatus = to Wai.responseStatus
  {-# INLINE getResponseStatus #-}
  getResponseHeaders = to Wai.responseHeaders
  {-# INLINE getResponseHeaders #-}

-- | Type class for values with a lens into a 'Response'.
class (GetResponse s) => HasResponse s where
  {-# MINIMAL setResponse #-}

  -- | Replace the 'Response' component.
  setResponse :: Response -> s -> s

  -- | A 'Lens'' into the 'Response' component.
  response :: Lens' s Response
  response = lens (view getResponse) (flip setResponse)
  {-# INLINE response #-}

  -- | A 'Lens'' on the 'Status'.
  responseStatus :: Lens' s Status
  responseStatus = response . responseStatus
  {-# INLINE responseStatus #-}

  -- | A 'Lens'' on the 'ResponseHeaders'.
  responseHeaders :: Lens' s ResponseHeaders
  responseHeaders = response . responseHeaders
  {-# INLINE responseHeaders #-}

instance HasResponse Response where
  setResponse = const
  {-# INLINE setResponse #-}
  responseStatus = lens Wai.responseStatus (\r s -> Wai.mapResponseStatus (const s) r)
  {-# INLINE responseStatus #-}
  responseHeaders = lens Wai.responseHeaders (\r h -> Wai.mapResponseHeaders (const h) r)
  {-# INLINE responseHeaders #-}

-- | Type class for values that can be constructed from a 'Response'.
class ReviewResponse t where
  -- | A 'Review' to construct a value from a 'Response'.
  reviewResponse :: Review t Response

instance ReviewResponse Response where
  reviewResponse = unto id
  {-# INLINE reviewResponse #-}

-- | Type class for values with a prism into a 'Response'.
class (ReviewResponse t) => AsResponse t where
  {-# MINIMAL matchResponse #-}

  -- | Attempt to extract a 'Response'.
  matchResponse :: t -> Maybe Response

  -- | A 'Prism'' into a 'Response'.
  _Response :: Prism' t Response
  _Response = prism' (review reviewResponse) matchResponse
  {-# INLINE _Response #-}

instance AsResponse Response where
  matchResponse = Just
  {-# INLINE matchResponse #-}

-- | A 'Setter'' on the 'Response' within an 'Application'.
--
-- Uses 'Wai.modifyResponse' under the hood. Allows modifying the response
-- that an 'Application' produces without manually threading the CPS callback.
applicationResponse :: Setter' Application Response
applicationResponse = sets Wai.modifyResponse
{-# INLINE applicationResponse #-}

-- ** ResponseFile

-- | Type class for values that can be constructed from a @ResponseFile@.
class ReviewResponseFile t where
  -- | A 'Review' to construct a value from @ResponseFile@ components.
  reviewResponseFile :: Review t (Status, ResponseHeaders, FilePath, Maybe FilePart)

instance ReviewResponseFile Response where
  reviewResponseFile = unto (\(s, h, fp, mfp) -> ResponseFile s h fp mfp)
  {-# INLINE reviewResponseFile #-}

-- | Type class for values with a prism into a @ResponseFile@.
class (ReviewResponseFile t) => AsResponseFile t where
  {-# MINIMAL matchResponseFile #-}

  -- | Attempt to extract @ResponseFile@ components.
  matchResponseFile :: t -> Maybe (Status, ResponseHeaders, FilePath, Maybe FilePart)

  -- | A 'Prism'' into @ResponseFile@ components.
  _ResponseFile :: Prism' t (Status, ResponseHeaders, FilePath, Maybe FilePart)
  _ResponseFile = prism' (review reviewResponseFile) matchResponseFile
  {-# INLINE _ResponseFile #-}

instance AsResponseFile Response where
  matchResponseFile = \case
    ResponseFile s h fp mfp -> Just (s, h, fp, mfp)
    _ -> Nothing
  {-# INLINE matchResponseFile #-}

-- ** ResponseBuilder

-- | Type class for values that can be constructed from a @ResponseBuilder@.
class ReviewResponseBuilder t where
  -- | A 'Review' to construct a value from @ResponseBuilder@ components.
  reviewResponseBuilder :: Review t (Status, ResponseHeaders, Builder)

instance ReviewResponseBuilder Response where
  reviewResponseBuilder = unto (\(s, h, b) -> ResponseBuilder s h b)
  {-# INLINE reviewResponseBuilder #-}

-- | Type class for values with a prism into a @ResponseBuilder@.
class (ReviewResponseBuilder t) => AsResponseBuilder t where
  {-# MINIMAL matchResponseBuilder #-}

  -- | Attempt to extract @ResponseBuilder@ components.
  matchResponseBuilder :: t -> Maybe (Status, ResponseHeaders, Builder)

  -- | A 'Prism'' into @ResponseBuilder@ components.
  _ResponseBuilder :: Prism' t (Status, ResponseHeaders, Builder)
  _ResponseBuilder = prism' (review reviewResponseBuilder) matchResponseBuilder
  {-# INLINE _ResponseBuilder #-}

instance AsResponseBuilder Response where
  matchResponseBuilder = \case
    ResponseBuilder s h b -> Just (s, h, b)
    _ -> Nothing
  {-# INLINE matchResponseBuilder #-}

-- ** ResponseStream

-- | Type class for values that can be constructed from a @ResponseStream@.
class ReviewResponseStream t where
  -- | A 'Review' to construct a value from @ResponseStream@ components.
  reviewResponseStream :: Review t (Status, ResponseHeaders, StreamingBody)

instance ReviewResponseStream Response where
  reviewResponseStream = unto (\(s, h, sb) -> ResponseStream s h sb)
  {-# INLINE reviewResponseStream #-}

-- | Type class for values with a prism into a @ResponseStream@.
class (ReviewResponseStream t) => AsResponseStream t where
  {-# MINIMAL matchResponseStream #-}

  -- | Attempt to extract @ResponseStream@ components.
  matchResponseStream :: t -> Maybe (Status, ResponseHeaders, StreamingBody)

  -- | A 'Prism'' into @ResponseStream@ components.
  _ResponseStream :: Prism' t (Status, ResponseHeaders, StreamingBody)
  _ResponseStream = prism' (review reviewResponseStream) matchResponseStream
  {-# INLINE _ResponseStream #-}

instance AsResponseStream Response where
  matchResponseStream = \case
    ResponseStream s h sb -> Just (s, h, sb)
    _ -> Nothing
  {-# INLINE matchResponseStream #-}

-- ** ResponseRaw

-- | Type class for values that can be constructed from a @ResponseRaw@.
class ReviewResponseRaw t where
  -- | A 'Review' to construct a value from @ResponseRaw@ components.
  reviewResponseRaw :: Review t (IO ByteString -> (ByteString -> IO ()) -> IO (), Response)

instance ReviewResponseRaw Response where
  reviewResponseRaw = unto (uncurry ResponseRaw)
  {-# INLINE reviewResponseRaw #-}

-- | Type class for values with a prism into a @ResponseRaw@.
class (ReviewResponseRaw t) => AsResponseRaw t where
  {-# MINIMAL matchResponseRaw #-}

  -- | Attempt to extract @ResponseRaw@ components.
  matchResponseRaw :: t -> Maybe (IO ByteString -> (ByteString -> IO ()) -> IO (), Response)

  -- | A 'Prism'' into @ResponseRaw@ components.
  _ResponseRaw :: Prism' t (IO ByteString -> (ByteString -> IO ()) -> IO (), Response)
  _ResponseRaw = prism' (review reviewResponseRaw) matchResponseRaw
  {-# INLINE _ResponseRaw #-}

instance AsResponseRaw Response where
  matchResponseRaw = \case
    ResponseRaw f r -> Just (f, r)
    _ -> Nothing
  {-# INLINE matchResponseRaw #-}