1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE StrictData #-}
3 {-# OPTIONS_GHC -fno-warn-orphans #-}
4 module Symantic.HTTP.Client
5 ( module Symantic.HTTP.Client
6 , module Symantic.HTTP.Client.Connection
9 import Data.Default.Class (Default(..))
10 import Data.Function (($), (.), id)
11 import Data.Functor ((<$>))
12 import Data.Maybe (Maybe(..))
13 import Data.Proxy (Proxy(..))
14 import Data.Semigroup (Semigroup(..))
15 import GHC.Exts (IsList(..))
16 import qualified Data.ByteString as BS
17 import qualified Data.ByteString.Base64 as BS64
18 import qualified Data.List as List
19 import qualified Data.List.NonEmpty as NonEmpty
20 import qualified Data.Sequence as Seq
21 import qualified Data.Text.Encoding as Text
22 import qualified Network.HTTP.Client as Client
23 import qualified Network.HTTP.Types as HTTP
24 import qualified Web.HttpApiData as Web
26 import Symantic.HTTP.API
27 import Symantic.HTTP.MIME
28 import Symantic.HTTP.Client.Connection
31 -- | @'Client' a k@ is a recipe to produce a 'ClientRequest'
32 -- from arguments 'a' (one per number of alternative routes).
34 -- 'Client' is analogous to a printf using a format customized for HTTP routing.
37 { unClient :: (ClientModifier -> k) -> a -- Right Kan extension
40 -- | @'client' api@ returns the 'ClientRequest'
41 -- builders from the given 'api'.
42 client :: Client api ClientRequest -> api
43 client (Client cmd) = cmd ($ def)
45 -- ** Type 'ClientModifier'
46 type ClientModifier = ClientRequest -> ClientRequest
48 instance Cat Client where
49 Client x <.> Client y = Client $ \k ->
50 x $ \fx -> y $ \fy -> k $ fy . fx
51 instance Alt Client where
52 Client x <!> Client y = Client $ \k ->
55 type AltMerge Client = (:!:)
56 Client x <!> Client y = Client $ \k ->
57 x (\cm -> let n:!:_ = k cm in n) :!:
58 y (\cm -> let _:!:n = k cm in n)
60 -- try = id -- FIXME: see what to do
61 instance Pro Client where
62 dimap _a2b b2a r = Client $ \k -> unClient r k . b2a
64 instance HTTP_Path Client where
65 type PathConstraint Client a = Web.ToHttpApiData a
66 segment s = Client $ \k -> k $ \req ->
67 req{ clientReqPath = clientReqPath req <> "/" <> Web.toEncodedUrlPiece s }
68 capture' _n = Client $ \k a -> k $ \req ->
69 req{ clientReqPath = clientReqPath req <> "/" <> Web.toEncodedUrlPiece a }
70 captureAll = Client $ \k ss -> k $ \req ->
72 List.foldl' (\acc s -> acc <> "/" <> Web.toEncodedUrlPiece s) "" $
75 instance HTTP_Header Client where
76 header n = Client $ \k v -> k $ \req ->
77 req{ clientReqHeaders = clientReqHeaders req Seq.|> (n, Web.toHeader v) }
78 instance HTTP_BasicAuth Client where
79 type BasicAuthArgs Client a k = BasicAuthUser -> BasicAuthPass -> k
80 basicAuth' realm = Client $ \k user pass -> k $ \req ->
81 req{ clientReqHeaders =
82 let user_pass = Text.encodeUtf8 $ user<>":"<>pass in
83 clientReqHeaders req Seq.|>
85 , Web.toHeader $ "Basic " <> BS64.encode user_pass
88 instance HTTP_Query Client where
89 type QueryConstraint Client a = Web.ToHttpApiData a
90 queryParams' n = Client $ \k vs -> k $ \req ->
91 req{ clientReqQueryString =
92 clientReqQueryString req <>
93 fromList ((\v -> (n, Just $ Text.encodeUtf8 $ Web.toQueryParam v)) <$> vs) }
94 instance HTTP_Version Client where
95 version v = Client $ \k -> k $ \req ->
96 req{clientReqHttpVersion = v}
97 newtype ClientBodyArg a (ts::[*]) = ClientBodyArg a
98 instance HTTP_Body Client where
99 type BodyArg Client = ClientBodyArg
100 type BodyConstraint Client a ts = MimeTypes ts (MimeEncodable a)
103 BodyConstraint repr a ts =>
105 repr (BodyArg repr a ts -> k) k
106 body'= Client $ \k (ClientBodyArg a) -> k $ \req ->
108 case NonEmpty.head (mimeTypes @ts) :: MimeType (MimeEncodable a) of
109 MimeType (mt::Proxy t) ->
111 ( Client.RequestBodyLBS $ mimeEncode mt a
114 instance HTTP_Response Client where
115 type ResponseConstraint Client a ts = MimeTypes ts (MimeDecodable a)
116 type ResponseArgs Client a ts = Proxy ts -> Proxy a -> ClientRequest
117 type Response Client = ClientRequest
120 ResponseConstraint repr a ts =>
123 repr (ResponseArgs repr a ts)
125 response m = Client $ \k Proxy Proxy -> k $ \req ->
127 { clientReqMethod = m
129 clientReqAccept req <>
130 fromList (toList $ mediaTypes @ts @(MimeDecodable a))
133 instance Web.ToHttpApiData BS.ByteString where
134 toUrlPiece = Web.toUrlPiece . Text.decodeUtf8