{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

module Http.Internal where

import qualified Control.Exception.Safe as Exception
import qualified Data.Aeson as Aeson
import qualified Data.ByteString
import qualified Data.ByteString.Lazy
import qualified Data.Dynamic as Dynamic
import Dict (Dict)
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Types.Header as Header
import qualified Network.Mime as Mime
import qualified Platform
import Prelude (IO)

-- | A handler for making HTTP requests.
data Handler = Handler
  { Handler
-> forall e expect.
   (Typeable expect, Typeable e) =>
   Request' e expect -> Task e expect
handlerRequest :: forall e expect. (Dynamic.Typeable expect, Dynamic.Typeable e) => Request' e expect -> Task e expect,
    Handler -> forall e a. (Manager -> Task e a) -> Task e a
handlerWithThirdParty :: forall e a. (HTTP.Manager -> Task e a) -> Task e a,
    Handler -> forall a. LogHandler -> (Manager -> IO a) -> IO a
handlerWithThirdPartyIO :: forall a. Platform.LogHandler -> (HTTP.Manager -> IO a) -> IO a
  }

-- | A simple request with the built-in 'Error' type.
type Request a = Request' Error a

-- | A custom request.
data Request' x a = Request
  { -- | The request method, like @"GET"@ or @"PUT"@.
    Request' x a -> Text
method :: Text,
    -- | A list of request headers.
    Request' x a -> [Header]
headers :: [Header],
    -- | The url, like @\"https://fishes.com/salmon\"@.
    Request' x a -> Text
url :: Text,
    -- | The request body.
    Request' x a -> Body
body :: Body,
    -- | The amount of microseconds you're willing to wait before giving up.
    Request' x a -> Maybe Int
timeout :: Maybe Int,
    -- | The type of response you expect back from the request.
    Request' x a -> Expect' x a
expect :: Expect' x a
  }

-- | An HTTP header for configuration requests.
newtype Header = Header {Header -> Header
unHeader :: Header.Header}
  deriving (Header -> Header -> Bool
(Header -> Header -> Bool)
-> (Header -> Header -> Bool) -> Eq Header
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Header -> Header -> Bool
$c/= :: Header -> Header -> Bool
== :: Header -> Header -> Bool
$c== :: Header -> Header -> Bool
Eq, Int -> Header -> ShowS
[Header] -> ShowS
Header -> String
(Int -> Header -> ShowS)
-> (Header -> String) -> ([Header] -> ShowS) -> Show Header
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Header] -> ShowS
$cshowList :: [Header] -> ShowS
show :: Header -> String
$cshow :: Header -> String
showsPrec :: Int -> Header -> ShowS
$cshowsPrec :: Int -> Header -> ShowS
Show)

-- | Represents the body of a Request.
data Body = Body
  { Body -> ByteString
bodyContents :: Data.ByteString.Lazy.ByteString,
    Body -> Maybe MimeType
bodyContentType :: Maybe Mime.MimeType
  }

-- | A simple logic for interpreting a response body with the built-in 'Error' type.
type Expect a = Expect' Error a

-- | Logic for interpreting a response body.
data Expect' x a where
  ExpectJson :: Aeson.FromJSON a => Expect a
  ExpectText :: Expect Text
  ExpectWhatever :: Expect ()
  ExpectTextResponse :: (Response Text -> Result x a) -> Expect' x a
  ExpectBytesResponse :: (Response Data.ByteString.ByteString -> Result x a) -> Expect' x a

-- | A 'Request' can fail in a couple of ways:
--
-- - 'BadUrl' means you did not provide a valid URL.
-- - 'Timeout' means it took too long to get a response.
-- - 'NetworkError' means the user turned off their wifi, went in a cave, etc.
-- - 'BadStatus' means you got a response back, but the status code indicates failure.
-- - 'BadBody' means you got a response back with a nice status code, but the body of the response was something unexpected. The 'Text' in this cse is the debugging message that explains what went wrong with your JSONT decoder or whatever.
data Error
  = BadUrl Text
  | Timeout
  | NetworkError Text
  | BadStatus Int
  | BadBody Text
  deriving ((forall x. Error -> Rep Error x)
-> (forall x. Rep Error x -> Error) -> Generic Error
forall x. Rep Error x -> Error
forall x. Error -> Rep Error x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Error x -> Error
$cfrom :: forall x. Error -> Rep Error x
Generic, Error -> Error -> Bool
(Error -> Error -> Bool) -> (Error -> Error -> Bool) -> Eq Error
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Error -> Error -> Bool
$c/= :: Error -> Error -> Bool
== :: Error -> Error -> Bool
$c== :: Error -> Error -> Bool
Eq, Int -> Error -> ShowS
[Error] -> ShowS
Error -> String
(Int -> Error -> ShowS)
-> (Error -> String) -> ([Error] -> ShowS) -> Show Error
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Error] -> ShowS
$cshowList :: [Error] -> ShowS
show :: Error -> String
$cshow :: Error -> String
showsPrec :: Int -> Error -> ShowS
$cshowsPrec :: Int -> Error -> ShowS
Show)

instance Exception.Exception Error

instance Aeson.ToJSON Error

-- | A 'Response' can come back a couple different ways:
--
-- - 'BadUrl_' — you did not provide a valid URL.
-- - 'Timeout_' — it took too long to get a response.
-- - 'NetworkError_' — the user turned off their wifi, went in a cave, etc.
-- - 'BadStatus_' — a response arrived, but the status code indicates failure.
-- - 'GoodStatus_' — a response arrived with a nice status code!
-- - The type of the body depends on whether you use expectStringResponse or expectBytesResponse.
data Response body
  = BadUrl_ Text
  | Timeout_
  | NetworkError_ Text
  | BadStatus_ Metadata body
  | GoodStatus_ Metadata body
  deriving ((forall x. Response body -> Rep (Response body) x)
-> (forall x. Rep (Response body) x -> Response body)
-> Generic (Response body)
forall x. Rep (Response body) x -> Response body
forall x. Response body -> Rep (Response body) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall body x. Rep (Response body) x -> Response body
forall body x. Response body -> Rep (Response body) x
$cto :: forall body x. Rep (Response body) x -> Response body
$cfrom :: forall body x. Response body -> Rep (Response body) x
Generic, Response body -> Response body -> Bool
(Response body -> Response body -> Bool)
-> (Response body -> Response body -> Bool) -> Eq (Response body)
forall body. Eq body => Response body -> Response body -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Response body -> Response body -> Bool
$c/= :: forall body. Eq body => Response body -> Response body -> Bool
== :: Response body -> Response body -> Bool
$c== :: forall body. Eq body => Response body -> Response body -> Bool
Eq, Int -> Response body -> ShowS
[Response body] -> ShowS
Response body -> String
(Int -> Response body -> ShowS)
-> (Response body -> String)
-> ([Response body] -> ShowS)
-> Show (Response body)
forall body. Show body => Int -> Response body -> ShowS
forall body. Show body => [Response body] -> ShowS
forall body. Show body => Response body -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Response body] -> ShowS
$cshowList :: forall body. Show body => [Response body] -> ShowS
show :: Response body -> String
$cshow :: forall body. Show body => Response body -> String
showsPrec :: Int -> Response body -> ShowS
$cshowsPrec :: forall body. Show body => Int -> Response body -> ShowS
Show)

instance (Dynamic.Typeable body, Show body) => Exception.Exception (Response body)

instance (Aeson.ToJSON body) => Aeson.ToJSON (Response body)

-- Extra information about the response:
--
-- Note: It is possible for a response to have the same header multiple times. In that case, all the values end up in a single entry in the headers dictionary. The values are separated by commas, following the rules outlined [here](https://stackoverflow.com/questions/4371328/are-duplicate-http-response-headers-acceptable).
data Metadata = Metadata
  { -- statusCode like 200 or 404
    Metadata -> Int
metadataStatusCode :: Int,
    -- statusText describing what the statusCode means a little
    Metadata -> Text
metadataStatusText :: Text,
    -- headers like Content-Length and Expires
    Metadata -> Dict Text Text
metadataHeaders :: Dict Text Text
  }
  deriving ((forall x. Metadata -> Rep Metadata x)
-> (forall x. Rep Metadata x -> Metadata) -> Generic Metadata
forall x. Rep Metadata x -> Metadata
forall x. Metadata -> Rep Metadata x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Metadata x -> Metadata
$cfrom :: forall x. Metadata -> Rep Metadata x
Generic, Metadata -> Metadata -> Bool
(Metadata -> Metadata -> Bool)
-> (Metadata -> Metadata -> Bool) -> Eq Metadata
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Metadata -> Metadata -> Bool
$c/= :: Metadata -> Metadata -> Bool
== :: Metadata -> Metadata -> Bool
$c== :: Metadata -> Metadata -> Bool
Eq, Int -> Metadata -> ShowS
[Metadata] -> ShowS
Metadata -> String
(Int -> Metadata -> ShowS)
-> (Metadata -> String) -> ([Metadata] -> ShowS) -> Show Metadata
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Metadata] -> ShowS
$cshowList :: [Metadata] -> ShowS
show :: Metadata -> String
$cshow :: Metadata -> String
showsPrec :: Int -> Metadata -> ShowS
$cshowsPrec :: Int -> Metadata -> ShowS
Show)

instance Aeson.ToJSON Metadata