1
1
module Network.HTTP.Affjax
2
- ( AJAX
3
- , Affjax
2
+ ( Affjax
4
3
, AffjaxRequest , defaultRequest
5
4
, AffjaxResponse
6
5
, URL
@@ -16,58 +15,53 @@ module Network.HTTP.Affjax
16
15
, retry
17
16
) where
18
17
19
- import Prelude hiding ( max )
18
+ import Prelude
20
19
21
- import Control.Monad.Aff (Aff , try , delay )
22
- import Control.Monad.Aff.Compat as AC
23
- import Control.Monad.Eff (kind Effect )
24
- import Control.Monad.Eff.Class (liftEff )
25
- import Control.Monad.Eff.Exception (Error , error )
26
- import Control.Monad.Eff.Ref (REF , newRef , readRef , writeRef )
27
20
import Control.Monad.Except (runExcept , throwError )
28
21
import Control.Parallel (parOneOf )
29
-
22
+ import Data.Argonaut.Core (Json )
23
+ import Data.Argonaut.Core as J
30
24
import Data.Argonaut.Parser (jsonParser )
25
+ import Data.Array (intercalate )
31
26
import Data.Array as Arr
32
27
import Data.Either (Either (..), either )
33
28
import Data.Foldable (any )
34
- import Data.Foreign ( F , Foreign , ForeignError (JSONError), fail , readString , toForeign )
29
+ import Data.FormURLEncoded as FormURLEncoded
35
30
import Data.Function (on )
36
31
import Data.Function.Uncurried (Fn2 , runFn2 )
37
32
import Data.HTTP.Method (Method (..), CustomMethod )
38
33
import Data.HTTP.Method as Method
39
34
import Data.Int (toNumber )
40
35
import Data.Maybe (Maybe (..))
41
- import Data.MediaType (MediaType )
42
36
import Data.Nullable (Nullable , toNullable )
43
37
import Data.Time.Duration (Milliseconds (..))
44
- import Data.Tuple (Tuple (..), fst , snd )
45
-
46
- import Math (max , pow )
47
-
48
- import Network.HTTP.Affjax.Request (class Requestable , RequestContent , toRequest )
49
- import Network.HTTP.Affjax.Response (class Respondable , ResponseContent , ResponseType (..), fromResponse , responseType , responseTypeToString )
38
+ import Effect.Aff (Aff , try , delay )
39
+ import Effect.Aff.Compat as AC
40
+ import Effect.Class (liftEffect )
41
+ import Effect.Exception (Error , error )
42
+ import Effect.Ref as Ref
43
+ import Foreign (F , Foreign , ForeignError (..), fail , renderForeignError , unsafeReadTagged , unsafeToForeign )
44
+ import Math as Math
45
+ import Network.HTTP.Affjax.Request as Request
46
+ import Network.HTTP.Affjax.Response as Response
50
47
import Network.HTTP.RequestHeader (RequestHeader (..), requestHeaderName , requestHeaderValue )
51
48
import Network.HTTP.ResponseHeader (ResponseHeader , responseHeader )
52
49
import Network.HTTP.StatusCode (StatusCode (..))
53
50
54
- -- | The effect type for AJAX requests made with Affjax.
55
- foreign import data AJAX :: Effect
56
-
57
- -- | The type for Affjax requests.
58
- type Affjax e a = Aff (ajax :: AJAX | e ) (AffjaxResponse a )
51
+ -- | The result type for Affjax requests.
52
+ type Affjax a = Aff (AffjaxResponse a )
59
53
60
- type AffjaxRequest a =
54
+ type AffjaxRequest =
61
55
{ method :: Either Method CustomMethod
62
56
, url :: URL
63
57
, headers :: Array RequestHeader
64
- , content :: Maybe a
58
+ , content :: Maybe Request.Request
65
59
, username :: Maybe String
66
60
, password :: Maybe String
67
61
, withCredentials :: Boolean
68
62
}
69
63
70
- defaultRequest :: AffjaxRequest Unit
64
+ defaultRequest :: AffjaxRequest
71
65
defaultRequest =
72
66
{ method: Left GET
73
67
, url: " /"
@@ -89,70 +83,70 @@ type AffjaxResponse a =
89
83
type URL = String
90
84
91
85
-- | Makes a `GET` request to the specified URL.
92
- get :: forall e a . Respondable a = > URL -> Affjax e a
93
- get u = affjax $ defaultRequest { url = u }
86
+ get :: forall a . Response.Response a - > URL -> Affjax a
87
+ get rt u = affjax rt $ defaultRequest { url = u }
94
88
95
89
-- | Makes a `POST` request to the specified URL, sending data.
96
- post :: forall e a b . Requestable a => Respondable b => URL -> a -> Affjax e b
97
- post u c = affjax $ defaultRequest { method = Left POST , url = u, content = Just c }
90
+ post :: forall a . Response.Response a -> URL -> Request.Request -> Affjax a
91
+ post rt u c = affjax rt $ defaultRequest { method = Left POST , url = u, content = Just c }
98
92
99
93
-- | Makes a `POST` request to the specified URL with the option to send data.
100
- post' :: forall e a b . Requestable a => Respondable b => URL -> Maybe a -> Affjax e b
101
- post' u c = affjax $ defaultRequest { method = Left POST , url = u, content = c }
94
+ post' :: forall a . Response.Response a -> URL -> Maybe Request.Request -> Affjax a
95
+ post' rt u c = affjax rt $ defaultRequest { method = Left POST , url = u, content = c }
102
96
103
97
-- | Makes a `POST` request to the specified URL, sending data and ignoring the
104
98
-- | response.
105
- post_ :: forall e a . Requestable a => URL -> a -> Affjax e Unit
106
- post_ = post
99
+ post_ :: URL -> Request.Request -> Affjax Unit
100
+ post_ = post Response .ignore
107
101
108
102
-- | Makes a `POST` request to the specified URL with the option to send data,
109
103
-- | and ignores the response.
110
- post_' :: forall e a . Requestable a => URL -> Maybe a -> Affjax e Unit
111
- post_' = post'
104
+ post_' :: URL -> Maybe Request.Request -> Affjax Unit
105
+ post_' = post' Response .ignore
112
106
113
107
-- | Makes a `PUT` request to the specified URL, sending data.
114
- put :: forall e a b . Requestable a => Respondable b => URL -> a -> Affjax e b
115
- put u c = affjax $ defaultRequest { method = Left PUT , url = u, content = Just c }
108
+ put :: forall a . Response.Response a -> URL -> Request.Request -> Affjax a
109
+ put rt u c = affjax rt $ defaultRequest { method = Left PUT , url = u, content = Just c }
116
110
117
111
-- | Makes a `PUT` request to the specified URL with the option to send data.
118
- put' :: forall e a b . Requestable a => Respondable b => URL -> Maybe a -> Affjax e b
119
- put' u c = affjax $ defaultRequest { method = Left PUT , url = u, content = c }
112
+ put' :: forall a . Response.Response a -> URL -> Maybe Request.Request -> Affjax a
113
+ put' rt u c = affjax rt $ defaultRequest { method = Left PUT , url = u, content = c }
120
114
121
115
-- | Makes a `PUT` request to the specified URL, sending data and ignoring the
122
116
-- | response.
123
- put_ :: forall e a . Requestable a => URL -> a -> Affjax e Unit
124
- put_ = put
117
+ put_ :: URL -> Request.Request -> Affjax Unit
118
+ put_ = put Response .ignore
125
119
126
120
-- | Makes a `PUT` request to the specified URL with the option to send data,
127
121
-- | and ignores the response.
128
- put_' :: forall e a . Requestable a => URL -> Maybe a -> Affjax e Unit
129
- put_' = put'
122
+ put_' :: URL -> Maybe Request.Request -> Affjax Unit
123
+ put_' = put' Response .ignore
130
124
131
125
-- | Makes a `DELETE` request to the specified URL.
132
- delete :: forall e a . Respondable a = > URL -> Affjax e a
133
- delete u = affjax $ defaultRequest { method = Left DELETE , url = u }
126
+ delete :: forall a . Response.Response a - > URL -> Affjax a
127
+ delete rt u = affjax rt $ defaultRequest { method = Left DELETE , url = u }
134
128
135
129
-- | Makes a `DELETE` request to the specified URL and ignores the response.
136
- delete_ :: forall e . URL -> Affjax e Unit
137
- delete_ = delete
130
+ delete_ :: URL -> Affjax Unit
131
+ delete_ = delete Response .ignore
138
132
139
133
-- | Makes a `PATCH` request to the specified URL, sending data.
140
- patch :: forall e a b . Requestable a => Respondable b => URL -> a -> Affjax e b
141
- patch u c = affjax $ defaultRequest { method = Left PATCH , url = u, content = Just c }
134
+ patch :: forall a . Response.Response a -> URL -> Request.Request -> Affjax a
135
+ patch rt u c = affjax rt $ defaultRequest { method = Left PATCH , url = u, content = Just c }
142
136
143
137
-- | Makes a `PATCH` request to the specified URL with the option to send data.
144
- patch' :: forall e a b . Requestable a => Respondable b => URL -> Maybe a -> Affjax e b
145
- patch' u c = affjax $ defaultRequest { method = Left PATCH , url = u, content = c }
138
+ patch' :: forall a . Response.Response a -> URL -> Maybe Request.Request -> Affjax a
139
+ patch' rt u c = affjax rt $ defaultRequest { method = Left PATCH , url = u, content = c }
146
140
147
141
-- | Makes a `PATCH` request to the specified URL, sending data and ignoring the
148
142
-- | response.
149
- patch_ :: forall e a . Requestable a => URL -> a -> Affjax e Unit
150
- patch_ = patch
143
+ patch_ :: URL -> Request.Request -> Affjax Unit
144
+ patch_ = patch Response .ignore
151
145
152
146
-- | Makes a `PATCH` request to the specified URL with the option to send data,
153
147
-- | and ignores the response.
154
- patch_' :: forall e a . Requestable a => URL -> Maybe a -> Affjax e Unit
155
- patch_' = patch'
148
+ patch_' :: URL -> Maybe Request.Request -> Affjax Unit
149
+ patch_' = patch' Response .ignore
156
150
157
151
-- | A sequence of retry delays, in milliseconds.
158
152
type RetryDelayCurve = Int -> Milliseconds
@@ -168,39 +162,34 @@ type RetryPolicy =
168
162
defaultRetryPolicy :: RetryPolicy
169
163
defaultRetryPolicy =
170
164
{ timeout : Nothing
171
- , delayCurve : \n -> Milliseconds $ max (30.0 * 1000.0 ) $ 100.0 * (pow 2.0 $ toNumber (n - 1 ))
165
+ , delayCurve : \n -> Milliseconds $ max (30.0 * 1000.0 ) $ 100.0 * (Math . pow 2.0 $ toNumber (n - 1 ))
172
166
, shouldRetryWithStatusCode : const false
173
167
}
174
168
175
169
-- | Either we have a failure (which may be an exception or a failed response), or we have a successful response.
176
170
type RetryState e a = Either (Either e a ) a
177
171
178
172
-- | Retry a request using a `RetryPolicy`. After the timeout, the last received response is returned; if it was not possible to communicate with the server due to an error, then this is bubbled up.
179
- retry
180
- :: forall e a b
181
- . Requestable a
182
- => RetryPolicy
183
- -> (AffjaxRequest a -> Affjax (ref :: REF | e ) b )
184
- -> (AffjaxRequest a -> Affjax (ref :: REF | e ) b )
173
+ retry :: forall a . RetryPolicy -> (AffjaxRequest -> Affjax a ) -> AffjaxRequest -> Affjax a
185
174
retry policy run req = do
186
175
-- failureRef is either an exception or a failed request
187
- failureRef <- liftEff $ newRef Nothing
176
+ failureRef <- liftEffect $ Ref .new Nothing
188
177
let loop = go failureRef
189
178
case policy.timeout of
190
179
Nothing -> loop 1
191
180
Just timeout -> do
192
181
result <- parOneOf [ Just <$> loop 1 , Nothing <$ delay timeout ]
193
182
case result of
194
183
Nothing -> do
195
- failure <- liftEff $ readRef failureRef
184
+ failure <- liftEffect $ Ref .read failureRef
196
185
case failure of
197
186
Nothing -> throwError $ error " Timeout"
198
187
Just failure' -> either throwError pure failure'
199
188
Just resp -> pure resp
200
189
where
201
190
retryState
202
- :: Either Error (AffjaxResponse b )
203
- -> RetryState Error (AffjaxResponse b )
191
+ :: Either Error (AffjaxResponse a )
192
+ -> RetryState Error (AffjaxResponse a )
204
193
retryState (Left exn) = Left $ Left exn
205
194
retryState (Right resp) =
206
195
case resp.status of
@@ -215,73 +204,81 @@ retry policy run req = do
215
204
result <- retryState <$> try (run req)
216
205
case result of
217
206
Left err -> do
218
- liftEff $ writeRef failureRef $ Just err
207
+ liftEffect $ Ref .write ( Just err) failureRef
219
208
delay (policy.delayCurve n)
220
209
go failureRef (n + 1 )
221
210
Right resp -> pure resp
222
211
223
212
-- | Makes an `Affjax` request.
224
- affjax
225
- :: forall e a b
226
- . Requestable a
227
- => Respondable b
228
- => AffjaxRequest a
229
- -> Affjax e b
230
- affjax req = do
231
- res <- AC .fromEffFnAff $ runFn2 _ajax responseHeader req'
232
- case res { response = _ } <$> runExcept (fromResponse' res.response) of
233
- Left err -> throwError $ error (show err)
234
- Right res' -> pure res'
213
+ affjax :: forall a . Response.Response a -> AffjaxRequest -> Affjax a
214
+ affjax rt req = do
215
+ res <- AC .fromEffectFnAff $ runFn2 _ajax responseHeader req'
216
+ case runExcept (fromResponse' res.response) of
217
+ Left err -> throwError $ error $ intercalate " \n " (map renderForeignError err)
218
+ Right res' -> pure (res { response = res' })
235
219
where
236
220
237
- req' :: AjaxRequest
221
+ req' :: AjaxRequest a
238
222
req' =
239
223
{ method: Method .print req.method
240
224
, url: req.url
241
- , headers: (\h -> { field: requestHeaderName h, value: requestHeaderValue h }) <$> headers
242
- , content: toNullable (snd requestSettings )
243
- , responseType: responseTypeToString (snd responseSettings)
225
+ , headers: (\h -> { field: requestHeaderName h, value: requestHeaderValue h }) <$> headers req.content
226
+ , content: toNullable (extractContent <$> req.content )
227
+ , responseType: Response .toResponseType rt
244
228
, username: toNullable req.username
245
229
, password: toNullable req.password
246
230
, withCredentials: req.withCredentials
247
231
}
248
232
249
- requestSettings :: Tuple (Maybe MediaType ) (Maybe RequestContent )
250
- requestSettings = case toRequest <$> req.content of
251
- Nothing -> Tuple Nothing Nothing
252
- Just (Tuple mime rt) -> Tuple mime (Just rt)
253
-
254
- responseSettings :: Tuple (Maybe MediaType ) (ResponseType b )
255
- responseSettings = responseType
256
-
257
- headers :: Array RequestHeader
258
- headers =
259
- addHeader (ContentType <$> fst requestSettings) $
260
- addHeader (Accept <$> fst responseSettings)
233
+ extractContent :: Request.Request -> Foreign
234
+ extractContent = case _ of
235
+ Request.ArrayView f → f unsafeToForeign
236
+ Request.Blob x → unsafeToForeign x
237
+ Request.Document x → unsafeToForeign x
238
+ Request.String x → unsafeToForeign x
239
+ Request.FormData x → unsafeToForeign x
240
+ Request.FormURLEncoded x → unsafeToForeign (FormURLEncoded .encode x)
241
+ Request.Json x → unsafeToForeign (J .stringify x)
242
+
243
+ headers :: Maybe Request.Request -> Array RequestHeader
244
+ headers reqContent =
245
+ addHeader (ContentType <$> (Request .toMediaType =<< reqContent)) $
246
+ addHeader (Accept <$> Response .toMediaType rt)
261
247
req.headers
262
248
263
249
addHeader :: Maybe RequestHeader -> Array RequestHeader -> Array RequestHeader
264
250
addHeader mh hs = case mh of
265
251
Just h | not $ any (on eq requestHeaderName h) hs -> hs `Arr.snoc` h
266
252
_ -> hs
267
253
268
- parseJSON :: String -> F Foreign
269
- parseJSON = either (fail <<< JSONError ) (pure <<< toForeign) <<< jsonParser
270
-
271
- fromResponse' :: ResponseContent -> F b
272
- fromResponse' = case snd responseSettings of
273
- JSONResponse -> fromResponse <=< parseJSON <=< readString
274
- _ -> fromResponse
275
-
276
- type AjaxRequest =
254
+ parseJSON :: String -> F Json
255
+ parseJSON = case _ of
256
+ " " -> pure J .jsonEmptyObject
257
+ str -> either (fail <<< ForeignError ) pure (jsonParser str)
258
+
259
+ fromResponse' :: Foreign -> F a
260
+ fromResponse' = case rt of
261
+ Response.ArrayBuffer _ -> unsafeReadTagged " ArrayBuffer"
262
+ Response.Blob _ -> unsafeReadTagged " Blob"
263
+ Response.Document _ -> unsafeReadTagged " Document"
264
+ Response.Json coe -> coe <<< parseJSON <=< unsafeReadTagged " String"
265
+ Response.String _ -> unsafeReadTagged " String"
266
+ Response.Ignore coe -> const $ coe (pure unit)
267
+
268
+ type AjaxRequest a =
277
269
{ method :: String
278
270
, url :: URL
279
271
, headers :: Array { field :: String , value :: String }
280
- , content :: Nullable RequestContent
272
+ , content :: Nullable Foreign
281
273
, responseType :: String
282
274
, username :: Nullable String
283
275
, password :: Nullable String
284
276
, withCredentials :: Boolean
285
277
}
286
278
287
- foreign import _ajax :: forall e . Fn2 (String -> String -> ResponseHeader ) AjaxRequest (AC.EffFnAff (ajax :: AJAX | e ) (AffjaxResponse Foreign ))
279
+ foreign import _ajax
280
+ :: forall a
281
+ . Fn2
282
+ (String -> String -> ResponseHeader )
283
+ (AjaxRequest a )
284
+ (AC.EffectFnAff (AffjaxResponse Foreign ))
0 commit comments