module Mig.Core.Class.Response (
Resp (..),
RespOr (..),
IsResp (..),
badReq,
internalServerError,
notImplemented,
redirect,
setHeader,
SetCookie (..),
defCookie,
setCookie,
) where
import Data.Bifunctor
import Data.ByteString (ByteString)
import Data.ByteString.Lazy qualified as BL
import Data.Kind
import Data.List qualified as List
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Time
import Network.HTTP.Media.RenderHeader (RenderHeader (..))
import Network.HTTP.Types.Header (HeaderName, ResponseHeaders, hSetCookie)
import Network.HTTP.Types.Status (Status, internalServerError500, notImplemented501, ok200, status302, status400)
import Web.HttpApiData
import Web.Internal.FormUrlEncoded
import Mig.Core.Class.MediaType (AnyMedia, MediaType, ToMediaType (..), ToRespBody (..))
import Mig.Core.Types.Http (Response, ResponseBody (..), noContentResponse)
import Mig.Core.Types.Http qualified as Response (Response (..))
import Mig.Core.Types.Http qualified as Types
data Resp (media :: Type) a = Resp
{ forall media a. Resp media a -> Status
status :: Status
, :: ResponseHeaders
, forall media a. Resp media a -> Maybe a
body :: Maybe a
}
deriving (Int -> Resp media a -> ShowS
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall media a. Show a => Int -> Resp media a -> ShowS
forall media a. Show a => [Resp media a] -> ShowS
forall media a. Show a => Resp media a -> String
showList :: [Resp media a] -> ShowS
$cshowList :: forall media a. Show a => [Resp media a] -> ShowS
show :: Resp media a -> String
$cshow :: forall media a. Show a => Resp media a -> String
showsPrec :: Int -> Resp media a -> ShowS
$cshowsPrec :: forall media a. Show a => Int -> Resp media a -> ShowS
Show, Resp media a -> Resp media a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall media a. Eq a => Resp media a -> Resp media a -> Bool
/= :: Resp media a -> Resp media a -> Bool
$c/= :: forall media a. Eq a => Resp media a -> Resp media a -> Bool
== :: Resp media a -> Resp media a -> Bool
$c== :: forall media a. Eq a => Resp media a -> Resp media a -> Bool
Eq, forall a b. a -> Resp media b -> Resp media a
forall a b. (a -> b) -> Resp media a -> Resp media b
forall media a b. a -> Resp media b -> Resp media a
forall media a b. (a -> b) -> Resp media a -> Resp media b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: forall a b. a -> Resp media b -> Resp media a
$c<$ :: forall media a b. a -> Resp media b -> Resp media a
fmap :: forall a b. (a -> b) -> Resp media a -> Resp media b
$cfmap :: forall media a b. (a -> b) -> Resp media a -> Resp media b
Functor)
newtype RespOr ty err a = RespOr {forall ty err a.
RespOr ty err a -> Either (Resp ty err) (Resp ty a)
unRespOr :: Either (Resp ty err) (Resp ty a)}
deriving (Int -> RespOr ty err a -> ShowS
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall ty err a.
(Show err, Show a) =>
Int -> RespOr ty err a -> ShowS
forall ty err a. (Show err, Show a) => [RespOr ty err a] -> ShowS
forall ty err a. (Show err, Show a) => RespOr ty err a -> String
showList :: [RespOr ty err a] -> ShowS
$cshowList :: forall ty err a. (Show err, Show a) => [RespOr ty err a] -> ShowS
show :: RespOr ty err a -> String
$cshow :: forall ty err a. (Show err, Show a) => RespOr ty err a -> String
showsPrec :: Int -> RespOr ty err a -> ShowS
$cshowsPrec :: forall ty err a.
(Show err, Show a) =>
Int -> RespOr ty err a -> ShowS
Show, RespOr ty err a -> RespOr ty err a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall ty err a.
(Eq err, Eq a) =>
RespOr ty err a -> RespOr ty err a -> Bool
/= :: RespOr ty err a -> RespOr ty err a -> Bool
$c/= :: forall ty err a.
(Eq err, Eq a) =>
RespOr ty err a -> RespOr ty err a -> Bool
== :: RespOr ty err a -> RespOr ty err a -> Bool
$c== :: forall ty err a.
(Eq err, Eq a) =>
RespOr ty err a -> RespOr ty err a -> Bool
Eq, forall a b. a -> RespOr ty err b -> RespOr ty err a
forall a b. (a -> b) -> RespOr ty err a -> RespOr ty err b
forall ty err a b. a -> RespOr ty err b -> RespOr ty err a
forall ty err a b. (a -> b) -> RespOr ty err a -> RespOr ty err b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: forall a b. a -> RespOr ty err b -> RespOr ty err a
$c<$ :: forall ty err a b. a -> RespOr ty err b -> RespOr ty err a
fmap :: forall a b. (a -> b) -> RespOr ty err a -> RespOr ty err b
$cfmap :: forall ty err a b. (a -> b) -> RespOr ty err a -> RespOr ty err b
Functor)
class IsResp a where
type RespBody a :: Type
type RespError a :: Type
type RespMedia a :: Type
ok :: RespBody a -> a
bad :: Status -> RespError a -> a
noContent :: Status -> a
:: ResponseHeaders -> a -> a
:: a -> ResponseHeaders
setStatus :: Status -> a -> a
getRespBody :: a -> Maybe (RespBody a)
getRespError :: a -> Maybe (RespError a)
getStatus :: a -> Status
setMedia :: MediaType -> a -> a
setMedia MediaType
media = forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders [(HeaderName
"Content-Type", forall h. RenderHeader h => h -> ByteString
renderHeader MediaType
media)]
getMedia :: MediaType
toResponse :: a -> Response
setHeader :: (IsResp a, ToHttpApiData h) => HeaderName -> h -> a -> a
HeaderName
name h
val = forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders [(HeaderName
name, forall a. ToHttpApiData a => a -> ByteString
toHeader h
val)]
instance (ToRespBody ty a) => IsResp (Resp ty a) where
type RespBody (Resp ty a) = a
type RespError (Resp ty a) = a
type RespMedia (Resp ty a) = ty
ok :: RespBody (Resp ty a) -> Resp ty a
ok = forall media a.
Status -> ResponseHeaders -> Maybe a -> Resp media a
Resp Status
ok200 [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just
bad :: Status -> RespError (Resp ty a) -> Resp ty a
bad Status
status = forall media a.
Status -> ResponseHeaders -> Maybe a -> Resp media a
Resp Status
status [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just
addHeaders :: ResponseHeaders -> Resp ty a -> Resp ty a
addHeaders ResponseHeaders
hs Resp ty a
x = Resp ty a
x{$sel:headers:Resp :: ResponseHeaders
headers = Resp ty a
x.headers forall a. Semigroup a => a -> a -> a
<> ResponseHeaders
hs}
getHeaders :: Resp ty a -> ResponseHeaders
getHeaders Resp ty a
x = Resp ty a
x.headers
noContent :: Status -> Resp ty a
noContent Status
st = forall media a.
Status -> ResponseHeaders -> Maybe a -> Resp media a
Resp Status
st [] forall a. Maybe a
Nothing
setStatus :: Status -> Resp ty a -> Resp ty a
setStatus Status
st Resp ty a
x = Resp ty a
x{$sel:status:Resp :: Status
status = Status
st}
getStatus :: Resp ty a -> Status
getStatus Resp ty a
x = Resp ty a
x.status
getMedia :: MediaType
getMedia = forall {k} (a :: k). ToMediaType a => MediaType
toMediaType @ty
getRespBody :: Resp ty a -> Maybe (RespBody (Resp ty a))
getRespBody Resp ty a
x = Resp ty a
x.body
getRespError :: Resp ty a -> Maybe (RespError (Resp ty a))
getRespError Resp ty a
x
| Resp ty a
x.status forall a. Eq a => a -> a -> Bool
== Status
ok200 = forall a. Maybe a
Nothing
| Bool
otherwise = Resp ty a
x.body
toResponse :: Resp ty a -> Response
toResponse Resp ty a
a = Status -> ResponseHeaders -> ResponseBody -> Response
Response.Response Resp ty a
a.status ResponseHeaders
headers ResponseBody
body
where
media :: MediaType
media = forall {k} (a :: k). ToMediaType a => MediaType
toMediaType @ty
headers :: ResponseHeaders
headers = Resp ty a
a.headers forall a. Semigroup a => a -> a -> a
<> [(HeaderName
"Content-Type", forall h. RenderHeader h => h -> ByteString
renderHeader MediaType
media)]
body :: ResponseBody
body = MediaType -> ByteString -> ResponseBody
RawResp MediaType
media (forall b a. b -> (a -> b) -> Maybe a -> b
maybe ByteString
"" (forall {k} (ty :: k) b. ToRespBody ty b => b -> ByteString
toRespBody @ty) Resp ty a
a.body)
instance IsResp Response where
type RespBody Response = BL.ByteString
type RespError Response = BL.ByteString
type RespMedia Response = AnyMedia
ok :: RespBody Response -> Response
ok = Status -> ResponseHeaders -> ResponseBody -> Response
Response.Response Status
ok200 [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. MediaType -> ByteString -> ResponseBody
RawResp MediaType
"*/*"
bad :: Status -> RespError Response -> Response
bad Status
st = Status -> ResponseHeaders -> ResponseBody -> Response
Response.Response Status
st [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. MediaType -> ByteString -> ResponseBody
RawResp MediaType
"*/*"
addHeaders :: ResponseHeaders -> Response -> Response
addHeaders ResponseHeaders
hs Response
x = Response
x{$sel:headers:Response :: ResponseHeaders
Response.headers = Response
x.headers forall a. Semigroup a => a -> a -> a
<> ResponseHeaders
hs}
noContent :: Status -> Response
noContent = Status -> Response
noContentResponse
setStatus :: Status -> Response -> Response
setStatus Status
st Response
x = Response
x{$sel:status:Response :: Status
Response.status = Status
st}
getMedia :: MediaType
getMedia = MediaType
"*/*"
getStatus :: Response -> Status
getStatus Response
x = Response
x.status
getHeaders :: Response -> ResponseHeaders
getHeaders Response
x = Response
x.headers
getRespBody :: Response -> Maybe (RespBody Response)
getRespBody Response
x = case Response
x.body of
RawResp MediaType
_ ByteString
res -> forall a. a -> Maybe a
Just ByteString
res
ResponseBody
_ -> forall a. Maybe a
Nothing
getRespError :: Response -> Maybe (RespError Response)
getRespError Response
x
| Response
x.status forall a. Eq a => a -> a -> Bool
== Status
ok200 = forall a. Maybe a
Nothing
| Bool
otherwise = forall a. IsResp a => a -> Maybe (RespBody a)
getRespBody Response
x
toResponse :: Response -> Response
toResponse = forall a. a -> a
id
setMedia :: MediaType -> Response -> Response
setMedia MediaType
media = forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders [(HeaderName
"Content-Type", forall h. RenderHeader h => h -> ByteString
renderHeader MediaType
media)] forall b c a. (b -> c) -> (a -> b) -> a -> c
. Response -> Response
updateBody
where
updateBody :: Response -> Response
updateBody Response
response = Response
response{$sel:body:Response :: ResponseBody
Response.body = ResponseBody -> ResponseBody
setBodyMedia Response
response.body}
setBodyMedia :: ResponseBody -> ResponseBody
setBodyMedia = \case
RawResp MediaType
_ ByteString
content -> MediaType -> ByteString -> ResponseBody
RawResp MediaType
media ByteString
content
ResponseBody
other -> ResponseBody
other
instance (ToRespBody ty err, ToRespBody ty a) => IsResp (RespOr ty err a) where
type RespBody (RespOr ty err a) = a
type RespError (RespOr ty err a) = err
type RespMedia (RespOr ty err a) = ty
ok :: RespBody (RespOr ty err a) -> RespOr ty err a
ok = forall ty err a.
Either (Resp ty err) (Resp ty a) -> RespOr ty err a
RespOr forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. b -> Either a b
Right forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall media a.
Status -> ResponseHeaders -> Maybe a -> Resp media a
Resp Status
ok200 [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just
bad :: Status -> RespError (RespOr ty err a) -> RespOr ty err a
bad Status
status = forall ty err a.
Either (Resp ty err) (Resp ty a) -> RespOr ty err a
RespOr forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. a -> Either a b
Left forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. IsResp a => Status -> RespError a -> a
bad Status
status
addHeaders :: ResponseHeaders -> RespOr ty err a -> RespOr ty err a
addHeaders ResponseHeaders
hs = forall ty err a.
Either (Resp ty err) (Resp ty a) -> RespOr ty err a
RespOr forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap (forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders ResponseHeaders
hs) (forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders ResponseHeaders
hs) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall ty err a.
RespOr ty err a -> Either (Resp ty err) (Resp ty a)
unRespOr
noContent :: Status -> RespOr ty err a
noContent Status
st = forall ty err a.
Either (Resp ty err) (Resp ty a) -> RespOr ty err a
RespOr forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right (forall a. IsResp a => Status -> a
noContent Status
st)
setStatus :: Status -> RespOr ty err a -> RespOr ty err a
setStatus Status
st = forall ty err a.
Either (Resp ty err) (Resp ty a) -> RespOr ty err a
RespOr forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap (forall a. IsResp a => Status -> a -> a
setStatus Status
st) (forall a. IsResp a => Status -> a -> a
setStatus Status
st) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall ty err a.
RespOr ty err a -> Either (Resp ty err) (Resp ty a)
unRespOr
getMedia :: MediaType
getMedia = forall {k} (a :: k). ToMediaType a => MediaType
toMediaType @ty
getStatus :: RespOr ty err a -> Status
getStatus (RespOr Either (Resp ty err) (Resp ty a)
x) = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (.status) (.status) Either (Resp ty err) (Resp ty a)
x
getHeaders :: RespOr ty err a -> ResponseHeaders
getHeaders (RespOr Either (Resp ty err) (Resp ty a)
x) = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (.headers) (forall media a. Resp media a -> ResponseHeaders
headers) Either (Resp ty err) (Resp ty a)
x
getRespBody :: RespOr ty err a -> Maybe (RespBody (RespOr ty err a))
getRespBody (RespOr Either (Resp ty err) (Resp ty a)
x) = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (forall a b. a -> b -> a
const forall a. Maybe a
Nothing) (.body) Either (Resp ty err) (Resp ty a)
x
getRespError :: RespOr ty err a -> Maybe (RespError (RespOr ty err a))
getRespError (RespOr Either (Resp ty err) (Resp ty a)
x) = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (.body) (forall a b. a -> b -> a
const forall a. Maybe a
Nothing) Either (Resp ty err) (Resp ty a)
x
toResponse :: RespOr ty err a -> Response
toResponse = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall a. IsResp a => a -> Response
toResponse forall a. IsResp a => a -> Response
toResponse forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall ty err a.
RespOr ty err a -> Either (Resp ty err) (Resp ty a)
unRespOr
badReq :: (IsResp a) => RespError a -> a
badReq :: forall a. IsResp a => RespError a -> a
badReq = forall a. IsResp a => Status -> RespError a -> a
bad Status
status400
internalServerError :: (IsResp a) => RespError a -> a
internalServerError :: forall a. IsResp a => RespError a -> a
internalServerError = forall a. IsResp a => Status -> RespError a -> a
bad Status
internalServerError500
notImplemented :: (IsResp a) => RespError a -> a
notImplemented :: forall a. IsResp a => RespError a -> a
notImplemented = forall a. IsResp a => Status -> RespError a -> a
bad Status
notImplemented501
redirect :: (IsResp a) => Text -> a
redirect :: forall a. IsResp a => Text -> a
redirect Text
url = forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders [(HeaderName
"Location", Text -> ByteString
Text.encodeUtf8 Text
url)] forall a b. (a -> b) -> a -> b
$ forall a. IsResp a => Status -> a
noContent Status
status302
setCookie :: (ToForm cookie, IsResp resp) => SetCookie cookie -> resp -> resp
setCookie :: forall cookie resp.
(ToForm cookie, IsResp resp) =>
SetCookie cookie -> resp -> resp
setCookie SetCookie cookie
cookie = forall a. IsResp a => ResponseHeaders -> a -> a
addHeaders [(HeaderName
hSetCookie, forall a. ToForm a => SetCookie a -> ByteString
renderSetCookie SetCookie cookie
cookie)]
data SetCookie a = SetCookie
{ forall a. SetCookie a -> a
cookie :: a
, forall a. SetCookie a -> Maybe UTCTime
expires :: Maybe UTCTime
, forall a. SetCookie a -> Maybe Text
domain :: Maybe Text
, forall a. SetCookie a -> Maybe Text
path :: Maybe Text
, forall a. SetCookie a -> Bool
secure :: Bool
, forall a. SetCookie a -> Bool
httpOnly :: Bool
}
deriving (Int -> SetCookie a -> ShowS
forall a. Show a => Int -> SetCookie a -> ShowS
forall a. Show a => [SetCookie a] -> ShowS
forall a. Show a => SetCookie a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SetCookie a] -> ShowS
$cshowList :: forall a. Show a => [SetCookie a] -> ShowS
show :: SetCookie a -> String
$cshow :: forall a. Show a => SetCookie a -> String
showsPrec :: Int -> SetCookie a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> SetCookie a -> ShowS
Show, SetCookie a -> SetCookie a -> Bool
forall a. Eq a => SetCookie a -> SetCookie a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SetCookie a -> SetCookie a -> Bool
$c/= :: forall a. Eq a => SetCookie a -> SetCookie a -> Bool
== :: SetCookie a -> SetCookie a -> Bool
$c== :: forall a. Eq a => SetCookie a -> SetCookie a -> Bool
Eq)
renderSetCookie :: (ToForm a) => SetCookie a -> ByteString
renderSetCookie :: forall a. ToForm a => SetCookie a -> ByteString
renderSetCookie SetCookie a
value =
forall a. Monoid a => [a] -> a
mconcat forall a b. (a -> b) -> a -> b
$
(ByteString -> ByteString
BL.toStrict forall a b. (a -> b) -> a -> b
$ Form -> ByteString
urlEncodeForm forall a b. (a -> b) -> a -> b
$ forall a. ToForm a => a -> Form
toForm SetCookie a
value.cookie)
forall a. a -> [a] -> [a]
: forall {a}. IsString a => [a] -> [a]
addColons
( forall a. [Maybe a] -> [a]
catMaybes
[ Text -> Text -> ByteString
param Text
"expires" forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> Text
fmtTime forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SetCookie a
value.expires
, Text -> Text -> ByteString
param Text
"domain" forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SetCookie a
value.domain
, Text -> Text -> ByteString
param Text
"path" forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SetCookie a
value.path
, forall {a}. a -> Bool -> Maybe a
flag ByteString
"secure" SetCookie a
value.secure
, forall {a}. a -> Bool -> Maybe a
flag ByteString
"httpOnly" SetCookie a
value.httpOnly
]
)
where
addColons :: [a] -> [a]
addColons [a]
xs
| forall (t :: * -> *) a. Foldable t => t a -> Bool
null [a]
xs = []
| Bool
otherwise = a
";" forall a. a -> [a] -> [a]
: forall a. a -> [a] -> [a]
List.intersperse a
";" [a]
xs
param :: Text -> Text -> ByteString
param Text
name Text
v = Text -> ByteString
Text.encodeUtf8 forall a b. (a -> b) -> a -> b
$ Text
name forall a. Semigroup a => a -> a -> a
<> Text
v
flag :: a -> Bool -> Maybe a
flag a
name = \case
Bool
True -> forall a. a -> Maybe a
Just a
name
Bool
False -> forall a. Maybe a
Nothing
fmtTime :: UTCTime -> Text
fmtTime :: UTCTime -> Text
fmtTime = String -> Text
Text.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall t. FormatTime t => TimeLocale -> String -> t -> String
formatTime TimeLocale
defaultTimeLocale String
expiresFormat
expiresFormat :: String
expiresFormat :: String
expiresFormat = String
"%a, %d-%b-%Y %X GMT"
defCookie :: a -> SetCookie a
defCookie :: forall a. a -> SetCookie a
defCookie a
val =
SetCookie
{ $sel:cookie:SetCookie :: a
cookie = a
val
, $sel:expires:SetCookie :: Maybe UTCTime
expires = forall a. Maybe a
Nothing
, $sel:domain:SetCookie :: Maybe Text
domain = forall a. Maybe a
Nothing
, $sel:path:SetCookie :: Maybe Text
path = forall a. Maybe a
Nothing
, $sel:secure:SetCookie :: Bool
secure = Bool
False
, $sel:httpOnly:SetCookie :: Bool
httpOnly = Bool
False
}