1 {-# LANGUAGE AllowAmbiguousTypes #-}
2 {-# LANGUAGE DefaultSignatures #-}
3 {-# LANGUAGE DeriveFunctor #-}
4 {-# LANGUAGE StrictData #-}
5 module Symantic.HTTP.API where
8 import Data.Either (Either(..))
9 import Data.Eq (Eq(..))
10 import Data.Functor (Functor)
11 import Data.Kind (Constraint)
12 import Data.Ord (Ord(..))
13 import Data.Proxy (Proxy)
14 import Data.String (String)
15 import Data.Text (Text)
16 import Prelude (and, pure)
18 import Text.Show (Show(..))
19 import qualified Control.Monad.Classes as MC
20 import qualified Data.ByteString as BS
21 import qualified Data.ByteString.Lazy as BSL
22 import qualified Network.HTTP.Types as HTTP
26 (<.>) :: repr a b -> repr b c -> repr a c; infixl 4 <.>
27 -- (.>) :: repr x y -> repr a c -> repr a c; infixl 4 .>
32 type AltMerge repr :: * -> * -> *
33 (<!>) :: repr a b -> repr c d -> repr (a:!:c) (AltMerge repr b d); infixl 3 <!>
35 (<!>) :: repr a k -> repr b k -> repr (a:!:b) k; infixl 3 <!>
36 -- try :: repr k k -> repr k k
37 -- option :: k -> repr k k -> repr k k
40 -- Like '(,)' but 'infixl'.
41 -- Used to get alternative commands from a 'Client'
42 -- or to supply alternative handlers to a 'Server'.
43 data (:!:) a b = a:!:b
47 -- | Mainly useful to write a combinator which is a specialization of another,
48 -- by calling it instead of rewriting its logic.
49 -- Because type @a@ is asked by a 'Client' but given to a 'Server',
50 -- both @a->b@ and @b->a@ are used. This is reminiscent of a 'Profunctor'.
51 -- Hence the names 'Pro' and 'dimap'.
53 dimap :: (a -> b) -> (b -> a) -> repr (a -> k) k -> repr (b -> k) k
55 -- * Class 'HTTP_Path'
56 class HTTP_Path repr where
57 type PathConstraint repr a :: Constraint
58 type PathConstraint repr a = ()
59 segment :: Segment -> repr k k
61 PathConstraint repr a =>
62 Name -> repr (a -> k) k
63 captureAll :: repr ([Segment] -> k) k
65 -- | Convenient wrapper of 'segment'.
66 (</>) :: Cat repr => HTTP_Path repr => Segment -> repr a b -> repr a b
67 (</>) n = (segment n <.>); infixr 5 </>
69 -- | Like |capture'| but with the type variable 'a' first instead or 'repr'
70 -- so it can be passed using 'TypeApplications' without adding a '@_' for 'repr'.
74 PathConstraint repr a =>
75 Name -> repr (a -> k) k
77 {-# INLINE capture #-}
83 -- * Class 'HTTP_Header'
84 class HTTP_Header repr where
85 header :: HTTP.HeaderName -> repr (HeaderValue -> k) k
87 type HeaderValue = BS.ByteString
89 -- * Class 'HTTP_Body'
90 class HTTP_Body repr where
91 type BodyArg repr a (ts::[*]) :: *
92 type BodyConstraint repr a (ts::[*]) :: Constraint
93 type BodyConstraint repr a ts = ()
96 BodyConstraint repr a ts =>
97 repr (BodyArg repr a ts -> k) k
99 -- | Like |body'| but with the type variables 'a' and 'ts' first instead or 'repr',
100 -- so it can be passed using 'TypeApplications' without adding a '@_' for 'repr'.
104 BodyConstraint repr a ts =>
105 repr (BodyArg repr a ts -> k) k
106 body = body' @repr @a @ts
109 -- * Class 'HTTP_BodyStream'
110 class HTTP_BodyStream repr where
111 type BodyStreamArg repr as (ts::[*]) framing :: *
112 type BodyStreamConstraint repr as (ts::[*]) framing :: Constraint
113 type BodyStreamConstraint repr as ts framing = ()
115 BodyStreamConstraint repr as ts framing =>
116 repr (BodyStreamArg repr as ts framing -> k) k
118 -- | Like |bodyStream'| but with the type variables 'as', 'ts' and 'framing'
119 -- first instead or 'repr', so it can be passed using 'TypeApplications'
120 -- without adding a '@_' for 'repr'.
122 forall as ts framing k repr.
123 HTTP_BodyStream repr =>
124 BodyStreamConstraint repr as ts framing =>
125 repr (BodyStreamArg repr as ts framing -> k) k
126 bodyStream = bodyStream' @repr @as @ts @framing
127 {-# INLINE bodyStream #-}
129 -- * Class 'HTTP_Query'
130 class HTTP_Query repr where
131 type QueryConstraint repr a :: Constraint
132 type QueryConstraint repr a = ()
134 QueryConstraint repr a =>
135 QueryName -> repr ([a] -> k) k
137 QueryConstraint repr Bool =>
138 QueryName -> repr (Bool -> k) k
141 QueryConstraint repr Bool =>
142 QueryName -> repr (Bool -> k) k
143 queryFlag n = dimap and pure (queryParams' n)
144 type QueryName = BS.ByteString
145 type QueryValue = BS.ByteString
150 QueryConstraint repr a =>
151 QueryName -> repr ([a] -> k) k
152 queryParams = queryParams'
153 {-# INLINE queryParams #-}
155 -- * Class 'HTTP_BasicAuth'
156 -- | <https://tools.ietf.org/html/rfc2617#section-2 Basic Access Authentication>
157 class HTTP_BasicAuth repr where
158 type BasicAuthConstraint repr a :: Constraint
159 type BasicAuthConstraint repr a = ()
160 type BasicAuthArgs repr a k :: *
162 BasicAuthConstraint repr a =>
163 BasicAuthRealm -> repr (BasicAuthArgs repr a k) k
167 HTTP_BasicAuth repr =>
168 BasicAuthConstraint repr a =>
170 repr (BasicAuthArgs repr a k) k
171 basicAuth = basicAuth' @repr @a @k
172 {-# INLINE basicAuth #-}
174 -- ** Type 'BasicAuth'
176 = BasicAuth_Authorized usr
177 | BasicAuth_BadPassword
178 | BasicAuth_NoSuchUser
179 | BasicAuth_Unauthorized
180 deriving (Eq, Show, Functor)
182 type BasicAuthRealm = Text
183 type BasicAuthUser = Text
184 type BasicAuthPass = Text
186 -- * Class 'HTTP_Version'
187 class HTTP_Version repr where
188 version :: HTTP.HttpVersion -> repr k k
190 -- * Class 'HTTP_Status'
191 class HTTP_Status repr where
192 status :: StatusIs -> repr (HTTP.Status -> k) k
194 -- ** Type 'StatusIs'
196 = StatusIsInformational
198 | StatusIsRedirection
199 | StatusIsClientError
200 | StatusIsServerError
201 | StatusIs HTTP.Status
202 deriving (Eq, Ord, Show)
203 statusIs :: StatusIs -> (HTTP.Status -> Bool)
205 StatusIsInformational -> HTTP.statusIsInformational
206 StatusIsSuccessful -> HTTP.statusIsSuccessful
207 StatusIsRedirection -> HTTP.statusIsRedirection
208 StatusIsClientError -> HTTP.statusIsClientError
209 StatusIsServerError -> HTTP.statusIsServerError
210 StatusIs x -> \y -> HTTP.statusCode x == HTTP.statusCode y
212 -- * Class 'HTTP_Response'
213 class HTTP_Response repr where
214 type ResponseConstraint repr a (ts::[*]) :: Constraint
215 type ResponseConstraint repr a ts = ()
216 type ResponseArgs repr a (ts::[*]) :: *
217 type Response repr :: *
219 ResponseConstraint repr a ts =>
221 repr (ResponseArgs repr a ts)
224 -- | Wrap 'response' by giving it the corresponding 'HTTP.Method',
225 -- and put the type variables 'a' then 'ts' first instead or 'repr'
226 -- so they can be passed using 'TypeApplications'
227 -- without adding a |@_| for 'repr'.
228 get,head,put,post,delete,trace,connect,options,patch ::
230 HTTP_Response repr =>
231 ResponseConstraint repr a ts =>
232 repr (ResponseArgs repr a ts)
234 get = response @repr @a @ts HTTP.methodGet
235 head = response @repr @a @ts HTTP.methodHead
236 put = response @repr @a @ts HTTP.methodPut
237 post = response @repr @a @ts HTTP.methodPost
238 delete = response @repr @a @ts HTTP.methodDelete
239 trace = response @repr @a @ts HTTP.methodTrace
240 connect = response @repr @a @ts HTTP.methodConnect
241 options = response @repr @a @ts HTTP.methodOptions
242 patch = response @repr @a @ts HTTP.methodPatch
247 {-# INLINE delete #-}
249 {-# INLINE connect #-}
250 {-# INLINE options #-}
253 -- * Class 'HTTP_ResponseStream'
254 class HTTP_ResponseStream repr where
255 type ResponseStreamConstraint repr as (ts::[*]) framing :: Constraint
256 type ResponseStreamConstraint repr as ts framing = ()
257 type ResponseStreamArgs repr as (ts::[*]) framing :: *
258 type ResponseStream repr :: *
260 ResponseStreamConstraint repr as ts framing =>
262 repr (ResponseStreamArgs repr as ts framing)
263 (ResponseStream repr)
265 getStream,headStream,putStream,postStream,deleteStream,traceStream,connectStream,optionsStream,patchStream ::
266 forall as ts framing repr.
267 HTTP_ResponseStream repr =>
268 ResponseStreamConstraint repr as ts framing =>
269 repr (ResponseStreamArgs repr as ts framing)
270 (ResponseStream repr)
271 getStream = responseStream @repr @as @ts @framing HTTP.methodGet
272 headStream = responseStream @repr @as @ts @framing HTTP.methodHead
273 putStream = responseStream @repr @as @ts @framing HTTP.methodPut
274 postStream = responseStream @repr @as @ts @framing HTTP.methodPost
275 deleteStream = responseStream @repr @as @ts @framing HTTP.methodDelete
276 traceStream = responseStream @repr @as @ts @framing HTTP.methodTrace
277 connectStream = responseStream @repr @as @ts @framing HTTP.methodConnect
278 optionsStream = responseStream @repr @as @ts @framing HTTP.methodOptions
279 patchStream = responseStream @repr @as @ts @framing HTTP.methodPatch
280 {-# INLINE getStream #-}
281 {-# INLINE headStream #-}
282 {-# INLINE putStream #-}
283 {-# INLINE postStream #-}
284 {-# INLINE deleteStream #-}
285 {-# INLINE traceStream #-}
286 {-# INLINE connectStream #-}
287 {-# INLINE optionsStream #-}
288 {-# INLINE patchStream #-}
291 -- ** Type family 'FramingMonad'
292 type family FramingMonad p :: * -> *
293 -- ** Type family 'FramingYield'
294 type family FramingYield p :: *
295 -- ** Type family 'FramingReturn'
296 type family FramingReturn p :: *
298 -- ** Class 'FramingEncode'
299 class FramingEncode framing p where
302 {-mimeEncode-}(FramingYield p -> BSL.ByteString) ->
303 p -> IO (Either (FramingReturn p) (BSL.ByteString, p))
305 -- ** Class 'FramingDecode'
306 class FramingDecode framing p where
309 FramingMonad p ~ m =>
311 {-mimeDecode-}(BSL.ByteString -> Either String (FramingYield p)) ->
314 -- ** Type 'NoFraming'
315 -- | A framing strategy that does not do any framing at all,
316 -- it just passes the input data.
317 -- Most of the time this will be used with binary data, such as files.
320 -- ** Type 'NewlineFraming'
321 -- | A simple framing strategy that has no header,
322 -- and inserts a newline character after each frame.
323 -- WARNING: this assumes that it is used with a Content-Type
324 -- that encodes without newlines (e.g. JSON).
327 -- ** Type 'NetstringFraming'
328 -- | The netstring framing strategy as defined by djb:
329 -- <http://cr.yp.to/proto/netstrings.txt>
331 -- Any string of 8-bit bytes may be encoded as @[len]":"[string]","@. Here
332 -- @[string]@ is the string and @[len]@ is a nonempty sequence of ASCII digits
333 -- giving the length of @[string]@ in decimal. The ASCII digits are @<30>@ for
334 -- 0, @<31>@ for 1, and so on up through @<39>@ for 9. Extra zeros at the front
335 -- of @[len]@ are prohibited: @[len]@ begins with @<30>@ exactly when
336 -- @[string]@ is empty.
338 -- For example, the string @"hello world!"@ is encoded as
339 -- @<31 32 3a 68 65 6c 6c 6f 20 77 6f 72 6c 64 21 2c>@,
340 -- i.e., @"12:hello world!,"@.
341 -- The empty string is encoded as @"0:,"@.
342 data NetstringFraming