{-# LANGUAGE DeriveDataTypeable #-}

-- |
-- Contains bindings to the Extism PDK HTTP interface
module Extism.PDK.HTTP where

import Data.ByteString as B
import Data.Word
import Extism.JSON (Nullable (..))
import qualified Extism.Manifest (HTTPRequest (..))
import Extism.PDK
import Extism.PDK.Bindings
import Extism.PDK.Memory
import Text.JSON (Result(..), decode, encode, makeObj)
import qualified Text.JSON.Generic

-- | HTTP Request
data Request = Request
  { Request -> String
url :: String,
    Request -> [(String, String)]
headers :: [(String, String)],
    Request -> String
method :: String
  }
  deriving (Text.JSON.Generic.Typeable, Typeable Request
Typeable Request =>
(forall (c :: * -> *).
 (forall d b. Data d => c (d -> b) -> d -> c b)
 -> (forall g. g -> c g) -> Request -> c Request)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c Request)
-> (Request -> Constr)
-> (Request -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c Request))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Request))
-> ((forall b. Data b => b -> b) -> Request -> Request)
-> (forall r r'.
    (r -> r' -> r)
    -> r -> (forall d. Data d => d -> r') -> Request -> r)
-> (forall r r'.
    (r' -> r -> r)
    -> r -> (forall d. Data d => d -> r') -> Request -> r)
-> (forall u. (forall d. Data d => d -> u) -> Request -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> Request -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> Request -> m Request)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Request -> m Request)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> Request -> m Request)
-> Data Request
Request -> Constr
Request -> DataType
(forall b. Data b => b -> b) -> Request -> Request
forall a.
Typeable a =>
(forall (c :: * -> *).
 (forall d b. Data d => c (d -> b) -> d -> c b)
 -> (forall g. g -> c g) -> a -> c a)
-> (forall (c :: * -> *).
    (forall b r. Data b => c (b -> r) -> c r)
    -> (forall r. r -> c r) -> Constr -> c a)
-> (a -> Constr)
-> (a -> DataType)
-> (forall (t :: * -> *) (c :: * -> *).
    Typeable t =>
    (forall d. Data d => c (t d)) -> Maybe (c a))
-> (forall (t :: * -> * -> *) (c :: * -> *).
    Typeable t =>
    (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c a))
-> ((forall b. Data b => b -> b) -> a -> a)
-> (forall r r'.
    (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall r r'.
    (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> a -> r)
-> (forall u. (forall d. Data d => d -> u) -> a -> [u])
-> (forall u. Int -> (forall d. Data d => d -> u) -> a -> u)
-> (forall (m :: * -> *).
    Monad m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> (forall (m :: * -> *).
    MonadPlus m =>
    (forall d. Data d => d -> m d) -> a -> m a)
-> Data a
forall u. Int -> (forall d. Data d => d -> u) -> Request -> u
forall u. (forall d. Data d => d -> u) -> Request -> [u]
forall r r'.
(r -> r' -> r)
-> r -> (forall d. Data d => d -> r') -> Request -> r
forall r r'.
(r' -> r -> r)
-> r -> (forall d. Data d => d -> r') -> Request -> r
forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Request -> m Request
forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Request -> m Request
forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Request
forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Request -> c Request
forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Request)
forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Request)
$cgfoldl :: forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Request -> c Request
gfoldl :: forall (c :: * -> *).
(forall d b. Data d => c (d -> b) -> d -> c b)
-> (forall g. g -> c g) -> Request -> c Request
$cgunfold :: forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Request
gunfold :: forall (c :: * -> *).
(forall b r. Data b => c (b -> r) -> c r)
-> (forall r. r -> c r) -> Constr -> c Request
$ctoConstr :: Request -> Constr
toConstr :: Request -> Constr
$cdataTypeOf :: Request -> DataType
dataTypeOf :: Request -> DataType
$cdataCast1 :: forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Request)
dataCast1 :: forall (t :: * -> *) (c :: * -> *).
Typeable t =>
(forall d. Data d => c (t d)) -> Maybe (c Request)
$cdataCast2 :: forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Request)
dataCast2 :: forall (t :: * -> * -> *) (c :: * -> *).
Typeable t =>
(forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Request)
$cgmapT :: (forall b. Data b => b -> b) -> Request -> Request
gmapT :: (forall b. Data b => b -> b) -> Request -> Request
$cgmapQl :: forall r r'.
(r -> r' -> r)
-> r -> (forall d. Data d => d -> r') -> Request -> r
gmapQl :: forall r r'.
(r -> r' -> r)
-> r -> (forall d. Data d => d -> r') -> Request -> r
$cgmapQr :: forall r r'.
(r' -> r -> r)
-> r -> (forall d. Data d => d -> r') -> Request -> r
gmapQr :: forall r r'.
(r' -> r -> r)
-> r -> (forall d. Data d => d -> r') -> Request -> r
$cgmapQ :: forall u. (forall d. Data d => d -> u) -> Request -> [u]
gmapQ :: forall u. (forall d. Data d => d -> u) -> Request -> [u]
$cgmapQi :: forall u. Int -> (forall d. Data d => d -> u) -> Request -> u
gmapQi :: forall u. Int -> (forall d. Data d => d -> u) -> Request -> u
$cgmapM :: forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Request -> m Request
gmapM :: forall (m :: * -> *).
Monad m =>
(forall d. Data d => d -> m d) -> Request -> m Request
$cgmapMp :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Request -> m Request
gmapMp :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Request -> m Request
$cgmapMo :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Request -> m Request
gmapMo :: forall (m :: * -> *).
MonadPlus m =>
(forall d. Data d => d -> m d) -> Request -> m Request
Text.JSON.Generic.Data)

-- | HTTP Response
data Response = Response
  { Response -> Int
statusCode :: Int,
    Response -> Memory
memory :: Memory
  }

-- | Creates a new 'Request'
newRequest :: String -> Request
newRequest :: String -> Request
newRequest String
url =
  String -> [(String, String)] -> String -> Request
Request String
url [] String
"GET"

-- | Update a 'Request' with the provided HTTP request method (GET, POST, PUT, DELETE, ...)
withMethod :: String -> Request -> Request
withMethod :: String -> Request -> Request
withMethod String
meth Request
req =
  Request
req {method = meth}

-- | Update a 'Request' with the provided HTTP request headers
withHeaders :: [(String, String)] -> Request -> Request
withHeaders :: [(String, String)] -> Request -> Request
withHeaders [(String, String)]
h Request
req =
  Request
req {headers = h}

-- | Access the Memory block associated with a 'Response'
responseMemory :: Response -> Memory
responseMemory :: Response -> Memory
responseMemory (Response Int
_ Memory
mem) = Memory
mem

-- | Get the 'Response' body as a 'ByteString'
responseByteString :: Response -> IO ByteString
responseByteString :: Response -> IO ByteString
responseByteString (Response Int
_ Memory
mem) = do
  Either String ByteString
a <- Memory -> IO (Either String ByteString)
forall a. FromBytes a => Memory -> IO (Either String a)
load Memory
mem
  case Either String ByteString
a of
    Left String
e -> String -> IO ByteString
forall a. HasCallStack => String -> a
error String
e
    Right ByteString
x -> ByteString -> IO ByteString
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ByteString
x

-- | Get the 'Response' body as a 'String'
responseString :: Response -> IO String
responseString :: Response -> IO String
responseString (Response Int
_ Memory
mem) = Memory -> IO String
loadString Memory
mem

-- | Get the 'Response' body as JSON
responseJSON :: (Text.JSON.Generic.Data a) => Response -> IO (Either String a)
responseJSON :: forall a. Data a => Response -> IO (Either String a)
responseJSON (Response Int
_ Memory
mem) = do
  Result JSValue
json <- String -> Result JSValue
forall a. JSON a => String -> Result a
decode (String -> Result JSValue) -> IO String -> IO (Result JSValue)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Memory -> IO String
loadString Memory
mem
  case Result JSValue
json of
    Ok JSValue
json ->
      case JSValue -> Result a
forall a. Data a => JSValue -> Result a
Text.JSON.Generic.fromJSON JSValue
json of
        Ok a
x -> Either String a -> IO (Either String a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either String a -> IO (Either String a))
-> Either String a -> IO (Either String a)
forall a b. (a -> b) -> a -> b
$ a -> Either String a
forall a b. b -> Either a b
Right a
x
        Error String
msg -> Either String a -> IO (Either String a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Either String a
forall a b. a -> Either a b
Left String
msg)
    Error String
msg -> Either String a -> IO (Either String a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Either String a
forall a b. a -> Either a b
Left String
msg)

-- | Get the 'Response' body and decode it
response :: (FromBytes a) => Response -> IO (Either String a)
response :: forall a. FromBytes a => Response -> IO (Either String a)
response (Response Int
_ Memory
mem) = Memory -> IO (Either String a)
forall a. FromBytes a => Memory -> IO (Either String a)
load Memory
mem

-- | Send HTTP request with an optional request body
sendRequestWithBody :: (ToBytes a) => Request -> a -> IO Response
sendRequestWithBody :: forall a. ToBytes a => Request -> a -> IO Response
sendRequestWithBody Request
req a
b = do
  Memory
body <- a -> IO Memory
forall a. ToBytes a => a -> IO Memory
alloc a
b
  let json :: String
json =
        HTTPRequest -> String
forall a. JSON a => a -> String
encode
          Extism.Manifest.HTTPRequest
            { url :: String
Extism.Manifest.url = Request -> String
url Request
req,
              headers :: Nullable [(String, String)]
Extism.Manifest.headers = [(String, String)] -> Nullable [(String, String)]
forall a. a -> Nullable a
NotNull ([(String, String)] -> Nullable [(String, String)])
-> [(String, String)] -> Nullable [(String, String)]
forall a b. (a -> b) -> a -> b
$ Request -> [(String, String)]
headers Request
req,
              method :: Nullable String
Extism.Manifest.method = String -> Nullable String
forall a. a -> Nullable a
NotNull (String -> Nullable String) -> String -> Nullable String
forall a b. (a -> b) -> a -> b
$ Request -> String
method Request
req
            }
  Memory
j <- String -> IO Memory
allocString String
json
  MemoryOffset
res <- MemoryOffset -> MemoryOffset -> IO MemoryOffset
extismHTTPRequest (Memory -> MemoryOffset
memoryOffset Memory
j) (Memory -> MemoryOffset
memoryOffset Memory
body)
  Memory -> IO ()
free Memory
j
  Memory -> IO ()
free Memory
body
  Int32
code <- IO Int32
extismHTTPStatusCode
  if MemoryOffset
res MemoryOffset -> MemoryOffset -> Bool
forall a. Eq a => a -> a -> Bool
== MemoryOffset
0
    then Response -> IO Response
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> Memory -> Response
Response (Int32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int32
code) (MemoryOffset -> MemoryOffset -> Memory
Memory MemoryOffset
0 MemoryOffset
0))
    else do
      Memory
mem <- MemoryOffset -> IO Memory
findMemory MemoryOffset
res
      Response -> IO Response
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> Memory -> Response
Response (Int32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int32
code) Memory
mem)

-- | Send HTTP request with an optional request body
sendRequest :: (ToBytes a) => Request -> Maybe a -> IO Response
sendRequest :: forall a. ToBytes a => Request -> Maybe a -> IO Response
sendRequest Request
req Maybe a
b =
  let json :: String
json =
        HTTPRequest -> String
forall a. JSON a => a -> String
encode
          Extism.Manifest.HTTPRequest
            { url :: String
Extism.Manifest.url = Request -> String
url Request
req,
              headers :: Nullable [(String, String)]
Extism.Manifest.headers = [(String, String)] -> Nullable [(String, String)]
forall a. a -> Nullable a
NotNull ([(String, String)] -> Nullable [(String, String)])
-> [(String, String)] -> Nullable [(String, String)]
forall a b. (a -> b) -> a -> b
$ Request -> [(String, String)]
headers Request
req,
              method :: Nullable String
Extism.Manifest.method = String -> Nullable String
forall a. a -> Nullable a
NotNull (String -> Nullable String) -> String -> Nullable String
forall a b. (a -> b) -> a -> b
$ Request -> String
method Request
req
            }
   in let bodyMem :: IO Memory
bodyMem = case Maybe a
b of
            Maybe a
Nothing -> Memory -> IO Memory
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Memory -> IO Memory) -> Memory -> IO Memory
forall a b. (a -> b) -> a -> b
$ MemoryOffset -> MemoryOffset -> Memory
Memory MemoryOffset
0 MemoryOffset
0
            Just a
b -> a -> IO Memory
forall a. ToBytes a => a -> IO Memory
alloc a
b
       in do
            Memory
body <- IO Memory
bodyMem
            Memory
j <- String -> IO Memory
allocString String
json
            MemoryOffset
res <- MemoryOffset -> MemoryOffset -> IO MemoryOffset
extismHTTPRequest (Memory -> MemoryOffset
memoryOffset Memory
j) (Memory -> MemoryOffset
memoryOffset Memory
body)
            Memory -> IO ()
free Memory
j
            Memory -> IO ()
free Memory
body
            Int32
code <- IO Int32
extismHTTPStatusCode
            if MemoryOffset
res MemoryOffset -> MemoryOffset -> Bool
forall a. Eq a => a -> a -> Bool
== MemoryOffset
0
              then Response -> IO Response
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> Memory -> Response
Response (Int32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int32
code) (MemoryOffset -> MemoryOffset -> Memory
Memory MemoryOffset
0 MemoryOffset
0))
              else do
                MemoryOffset
len <- MemoryOffset -> IO MemoryOffset
extismLengthUnsafe MemoryOffset
res
                Response -> IO Response
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Int -> Memory -> Response
Response (Int32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int32
code) (MemoryOffset -> MemoryOffset -> Memory
Memory MemoryOffset
res MemoryOffset
len))