Skip to content

Commit 36e8b71

Browse files
authored
Merge pull request #127 from slamdata/next
More changes for the next release version
2 parents dc23aaf + d93ebc5 commit 36e8b71

15 files changed

+210
-153
lines changed

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
language: node_js
22
dist: trusty
33
sudo: required
4-
node_js: 6
4+
node_js: stable
55
install:
66
- npm install bower -g
77
- npm install
88
script:
99
- bower install --production
1010
- npm run -s build
1111
- bower install
12-
- node ./test-server.js &
13-
- sleep 2 && npm -s test
12+
- npm run -s test
1413
after_success:
1514
- >-
1615
test $TRAVIS_TAG &&

README.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,36 +28,49 @@ module Main where
2828
2929
import Prelude
3030
31+
import Affjax as AX
32+
import Affjax.ResponseFormat as ResponseFormat
3133
import Data.Argonaut.Core as J
3234
import Data.Either (Either(..))
3335
import Data.HTTP.Method (Method(..))
3436
import Effect.Aff (launchAff)
35-
import Effect.Class (liftEffect)
36-
import Effect.Console (log)
37-
import Network.HTTP.Affjax as AX
38-
import Network.HTTP.Affjax.ResponseFormat as ResponseFormat
37+
import Effect.Class.Console (log)
3938
4039
main = launchAff $ do
4140
res <- AX.request ResponseFormat.json (AX.defaultRequest { url = "/api", method = Left GET })
42-
liftEffect $ log $ "GET /api response: " <> J.stringify res.response
41+
case res.body of
42+
Left err -> log $ "GET /api response failed to decode: " <> AX.printResponseFormatError err
43+
Right json -> log $ "GET /api response: " <> J.stringify json
4344
```
4445

4546
(`defaultRequest` is a record value that has all the required fields pre-set for convenient overriding when making a request.)
4647

47-
Or use of a number of helpers for common cases:
48+
There are also a number of helpers for common `get`, `post`, `put`, `delete`, and `patch` cases:
4849

4950
```purescript
50-
import Network.HTTP.Affjax.RequestBody as RequestBody
51+
import Affjax.RequestBody as RequestBody
5152
5253
main = launchAff $ do
5354
res1 <- AX.get ResponseFormat.json "/api"
54-
liftEffect $ log $ "GET /api response: " <> J.stringify res1.response
55+
case res1.body of
56+
Left err -> log $ "GET /api response failed to decode: " <> AX.printResponseFormatError err
57+
Right json -> log $ "GET /api response: " <> J.stringify json
5558
5659
res2 <- AX.post ResponseFormat.json "/api" (RequestBody.json (J.fromString "test"))
57-
liftEffect $ log $ "POST /api response: " <> J.stringify res2.response
60+
case res2.body of
61+
Left err -> log $ "POST /api response failed to decode: " <> AX.printResponseFormatError err
62+
Right json -> log $ "POST /api response: " <> J.stringify json
5863
```
5964

60-
See the module documentation for a full list of these helpers.
65+
See the [main module documentation](https://pursuit.purescript.org/packages/purescript-affjax/docs/Affjax) for a full list of these helpers and their variations.
66+
67+
## Error handling
68+
69+
There are two ways an Affjax request can fail: there's either some problem with the request itself, or the result that comes back is not as expected.
70+
71+
For the first case, these errors will be things like the URL being invalid or the server not existing, and will occur in the `Aff` error channel. The [`try`](https://pursuit.purescript.org/packages/purescript-aff/docs/Effect.Aff#v:try) function can lift these errors out of the error channel so the result of a request becomes `Aff (Either Error (Response _))`.
72+
73+
The latter case occurs when we did get a response for the request, but the result that came back could not be handled in the way that was expected. In these situations the `body` value of the `Response` will be a `Left` value with the error message describing what went wrong.
6174

6275
## Module documentation
6376

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"express": "^4.14.0",
1212
"pulp": "^11.0.0",
1313
"purescript-psa": "^0.5.0",
14-
"purescript": "slamdata/node-purescript#0.12",
14+
"purescript": "^0.12.0",
1515
"rimraf": "^2.5.4",
1616
"xhr2": "^0.1.3"
1717
}

src/Network/HTTP/Affjax.js renamed to src/Affjax.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ exports._ajax = function () {
6868
var i = header.indexOf(":");
6969
return mkHeader(header.substring(0, i))(header.substring(i + 2));
7070
}),
71-
response: platformSpecific.getResponse(xhr)
71+
body: platformSpecific.getResponse(xhr)
7272
});
7373
};
7474
xhr.responseType = options.responseType;

src/Network/HTTP/Affjax.purs renamed to src/Affjax.purs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Network.HTTP.Affjax
1+
module Affjax
22
( RequestOptions, defaultRequest
33
, Response
44
, URL
@@ -12,16 +12,22 @@ module Network.HTTP.Affjax
1212
, RetryPolicy(..)
1313
, defaultRetryPolicy
1414
, retry
15+
, module Affjax.ResponseFormat
1516
) where
1617

1718
import Prelude
1819

20+
import Affjax.RequestBody as RequestBody
21+
import Affjax.RequestHeader (RequestHeader(..), requestHeaderName, requestHeaderValue)
22+
import Affjax.ResponseFormat (ResponseFormatError(..), printResponseFormatError)
23+
import Affjax.ResponseFormat as ResponseFormat
24+
import Affjax.ResponseHeader (ResponseHeader, responseHeader)
25+
import Affjax.StatusCode (StatusCode(..))
1926
import Control.Monad.Except (runExcept, throwError)
2027
import Control.Parallel (parOneOf)
2128
import Data.Argonaut.Core (Json)
2229
import Data.Argonaut.Core as J
2330
import Data.Argonaut.Parser (jsonParser)
24-
import Data.Array (intercalate)
2531
import Data.Array as Arr
2632
import Data.Either (Either(..), either)
2733
import Data.Foldable (any)
@@ -31,6 +37,7 @@ import Data.Function.Uncurried (Fn2, runFn2)
3137
import Data.HTTP.Method (Method(..), CustomMethod)
3238
import Data.HTTP.Method as Method
3339
import Data.Int (toNumber)
40+
import Data.List.NonEmpty as NEL
3441
import Data.Maybe (Maybe(..))
3542
import Data.Nullable (Nullable, toNullable)
3643
import Data.Time.Duration (Milliseconds(..))
@@ -39,13 +46,8 @@ import Effect.Aff.Compat as AC
3946
import Effect.Class (liftEffect)
4047
import Effect.Exception (Error, error)
4148
import Effect.Ref as Ref
42-
import Foreign (F, Foreign, ForeignError(..), fail, renderForeignError, unsafeReadTagged, unsafeToForeign)
49+
import Foreign (F, Foreign, ForeignError(..), fail, unsafeReadTagged, unsafeToForeign)
4350
import Math as Math
44-
import Network.HTTP.Affjax.RequestBody as RequestBody
45-
import Network.HTTP.Affjax.ResponseFormat as ResponseFormat
46-
import Network.HTTP.RequestHeader (RequestHeader(..), requestHeaderName, requestHeaderValue)
47-
import Network.HTTP.ResponseHeader (ResponseHeader, responseHeader)
48-
import Network.HTTP.StatusCode (StatusCode(..))
4951

5052
-- | A record that contains all the information to perform an HTTP request.
5153
-- | Instead of constructing the record from scratch it is often easier to build
@@ -87,77 +89,77 @@ type Response a =
8789
{ status :: StatusCode
8890
, statusText :: String
8991
, headers :: Array ResponseHeader
90-
, response :: a
92+
, body :: a
9193
}
9294

9395
-- | Type alias for URL strings to aid readability of types.
9496
type URL = String
9597

9698
-- | Makes a `GET` request to the specified URL.
97-
get :: forall a. ResponseFormat.ResponseFormat a -> URL -> Aff (Response a)
99+
get :: forall a. ResponseFormat.ResponseFormat a -> URL -> Aff (Response (Either ResponseFormatError a))
98100
get rt u = request rt $ defaultRequest { url = u }
99101

100102
-- | Makes a `POST` request to the specified URL, sending data.
101-
post :: forall a. ResponseFormat.ResponseFormat a -> URL -> RequestBody.RequestBody -> Aff (Response a)
103+
post :: forall a. ResponseFormat.ResponseFormat a -> URL -> RequestBody.RequestBody -> Aff (Response (Either ResponseFormatError a))
102104
post rt u c = request rt $ defaultRequest { method = Left POST, url = u, content = Just c }
103105

104106
-- | Makes a `POST` request to the specified URL with the option to send data.
105-
post' :: forall a. ResponseFormat.ResponseFormat a -> URL -> Maybe RequestBody.RequestBody -> Aff (Response a)
107+
post' :: forall a. ResponseFormat.ResponseFormat a -> URL -> Maybe RequestBody.RequestBody -> Aff (Response (Either ResponseFormatError a))
106108
post' rt u c = request rt $ defaultRequest { method = Left POST, url = u, content = c }
107109

108110
-- | Makes a `POST` request to the specified URL, sending data and ignoring the
109111
-- | response.
110112
post_ :: URL -> RequestBody.RequestBody -> Aff (Response Unit)
111-
post_ = post ResponseFormat.ignore
113+
post_ url = map (_ { body = unit }) <<< post ResponseFormat.ignore url
112114

113115
-- | Makes a `POST` request to the specified URL with the option to send data,
114116
-- | and ignores the response.
115117
post_' :: URL -> Maybe RequestBody.RequestBody -> Aff (Response Unit)
116-
post_' = post' ResponseFormat.ignore
118+
post_' url = map (_ { body = unit }) <<< post' ResponseFormat.ignore url
117119

118120
-- | Makes a `PUT` request to the specified URL, sending data.
119-
put :: forall a. ResponseFormat.ResponseFormat a -> URL -> RequestBody.RequestBody -> Aff (Response a)
121+
put :: forall a. ResponseFormat.ResponseFormat a -> URL -> RequestBody.RequestBody -> Aff (Response (Either ResponseFormatError a))
120122
put rt u c = request rt $ defaultRequest { method = Left PUT, url = u, content = Just c }
121123

122124
-- | Makes a `PUT` request to the specified URL with the option to send data.
123-
put' :: forall a. ResponseFormat.ResponseFormat a -> URL -> Maybe RequestBody.RequestBody -> Aff (Response a)
125+
put' :: forall a. ResponseFormat.ResponseFormat a -> URL -> Maybe RequestBody.RequestBody -> Aff (Response (Either ResponseFormatError a))
124126
put' rt u c = request rt $ defaultRequest { method = Left PUT, url = u, content = c }
125127

126128
-- | Makes a `PUT` request to the specified URL, sending data and ignoring the
127129
-- | response.
128130
put_ :: URL -> RequestBody.RequestBody -> Aff (Response Unit)
129-
put_ = put ResponseFormat.ignore
131+
put_ url = map (_ { body = unit }) <<< put ResponseFormat.ignore url
130132

131133
-- | Makes a `PUT` request to the specified URL with the option to send data,
132134
-- | and ignores the response.
133135
put_' :: URL -> Maybe RequestBody.RequestBody -> Aff (Response Unit)
134-
put_' = put' ResponseFormat.ignore
136+
put_' url = map (_ { body = unit }) <<< put' ResponseFormat.ignore url
135137

136138
-- | Makes a `DELETE` request to the specified URL.
137-
delete :: forall a. ResponseFormat.ResponseFormat a -> URL -> Aff (Response a)
139+
delete :: forall a. ResponseFormat.ResponseFormat a -> URL -> Aff (Response (Either ResponseFormatError a))
138140
delete rt u = request rt $ defaultRequest { method = Left DELETE, url = u }
139141

140142
-- | Makes a `DELETE` request to the specified URL and ignores the response.
141143
delete_ :: URL -> Aff (Response Unit)
142-
delete_ = delete ResponseFormat.ignore
144+
delete_ = map (_ { body = unit }) <<< delete ResponseFormat.ignore
143145

144146
-- | Makes a `PATCH` request to the specified URL, sending data.
145-
patch :: forall a. ResponseFormat.ResponseFormat a -> URL -> RequestBody.RequestBody -> Aff (Response a)
147+
patch :: forall a. ResponseFormat.ResponseFormat a -> URL -> RequestBody.RequestBody -> Aff (Response (Either ResponseFormatError a))
146148
patch rt u c = request rt $ defaultRequest { method = Left PATCH, url = u, content = Just c }
147149

148150
-- | Makes a `PATCH` request to the specified URL with the option to send data.
149-
patch' :: forall a. ResponseFormat.ResponseFormat a -> URL -> Maybe RequestBody.RequestBody -> Aff (Response a)
151+
patch' :: forall a. ResponseFormat.ResponseFormat a -> URL -> Maybe RequestBody.RequestBody -> Aff (Response (Either ResponseFormatError a))
150152
patch' rt u c = request rt $ defaultRequest { method = Left PATCH, url = u, content = c }
151153

152154
-- | Makes a `PATCH` request to the specified URL, sending data and ignoring the
153155
-- | response.
154156
patch_ :: URL -> RequestBody.RequestBody -> Aff (Response Unit)
155-
patch_ = patch ResponseFormat.ignore
157+
patch_ url = map (_ { body = unit }) <<< patch ResponseFormat.ignore url
156158

157159
-- | Makes a `PATCH` request to the specified URL with the option to send data,
158160
-- | and ignores the response.
159161
patch_' :: URL -> Maybe RequestBody.RequestBody -> Aff (Response Unit)
160-
patch_' = patch' ResponseFormat.ignore
162+
patch_' url = map (_ { body = unit }) <<< patch' ResponseFormat.ignore url
161163

162164
-- | A sequence of retry delays, in milliseconds.
163165
type RetryDelayCurve = Int -> Milliseconds
@@ -236,12 +238,14 @@ retry policy run req = do
236238
-- | ```purescript
237239
-- | get json "/resource"
238240
-- | ```
239-
request :: forall a. ResponseFormat.ResponseFormat a -> RequestOptions -> Aff (Response a)
241+
request :: forall a. ResponseFormat.ResponseFormat a -> RequestOptions -> Aff (Response (Either ResponseFormatError a))
240242
request rt req = do
241243
res <- AC.fromEffectFnAff $ runFn2 _ajax responseHeader req'
242-
case runExcept (fromResponse' res.response) of
243-
Left err -> throwError $ error $ intercalate "\n" (map renderForeignError err)
244-
Right res' -> pure (res { response = res' })
244+
case runExcept (fromResponse' res.body) of
245+
Left err -> do
246+
pure (res { body = Left (ResponseFormatError (NEL.head err) res.body) })
247+
Right res' -> do
248+
pure (res { body = Right res' })
245249
where
246250

247251
req' :: AjaxRequest a

src/Network/HTTP/Affjax/RequestBody.purs renamed to src/Affjax/RequestBody.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Network.HTTP.Affjax.RequestBody where
1+
module Affjax.RequestBody where
22

33
import Data.Argonaut.Core (Json)
44
import Data.ArrayBuffer.Types as A

src/Network/HTTP/RequestHeader.purs renamed to src/Affjax/RequestHeader.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Network.HTTP.RequestHeader where
1+
module Affjax.RequestHeader where
22

33
import Prelude
44

src/Network/HTTP/Affjax/ResponseFormat.purs renamed to src/Affjax/ResponseFormat.purs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Network.HTTP.Affjax.ResponseFormat where
1+
module Affjax.ResponseFormat where
22

33
import Prelude
44

@@ -7,6 +7,8 @@ import Data.ArrayBuffer.Types (ArrayBuffer)
77
import Data.Maybe (Maybe(..))
88
import Data.MediaType (MediaType)
99
import Data.MediaType.Common (applicationJSON)
10+
import Foreign (Foreign, ForeignError)
11+
import Foreign as Foreign
1012
import Web.DOM.Document (Document)
1113
import Web.File.Blob (Blob)
1214

@@ -54,3 +56,12 @@ toMediaType =
5456
case _ of
5557
Json _ -> Just applicationJSON
5658
_ -> Nothing
59+
60+
-- | Used when an error occurs when attempting to decode into a particular
61+
-- | response format. The error that occurred when decoding is included, along
62+
-- | with the value that decoding was attempted on.
63+
data ResponseFormatError = ResponseFormatError ForeignError Foreign
64+
65+
printResponseFormatError :: ResponseFormatError String
66+
printResponseFormatError (ResponseFormatError err _) =
67+
Foreign.renderForeignError err

src/Network/HTTP/ResponseHeader.purs renamed to src/Affjax/ResponseHeader.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Network.HTTP.ResponseHeader
1+
module Affjax.ResponseHeader
22
( ResponseHeader()
33
, responseHeader
44
, responseHeaderName

src/Network/HTTP/StatusCode.purs renamed to src/Affjax/StatusCode.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Network.HTTP.StatusCode where
1+
module Affjax.StatusCode where
22

33
import Prelude
44

test-server.js

Lines changed: 0 additions & 39 deletions
This file was deleted.

test/DocExamples.purs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@ module Test.DocExamples where
22

33
import Prelude
44

5+
import Affjax as AX
6+
import Affjax.RequestBody as RequestBody
7+
import Affjax.ResponseFormat as ResponseFormat
58
import Data.Argonaut.Core as J
69
import Data.Either (Either(..))
710
import Data.HTTP.Method (Method(..))
11+
import Effect (Effect)
812
import Effect.Aff (launchAff)
9-
import Effect.Class (liftEffect)
10-
import Effect.Console (log)
11-
import Network.HTTP.Affjax as AX
12-
import Network.HTTP.Affjax.RequestBody as RequestBody
13-
import Network.HTTP.Affjax.ResponseFormat as ResponseFormat
13+
import Effect.Class.Console (log)
1414

15-
main = launchAff $ do
15+
main :: Effect Unit
16+
main = void $ launchAff $ do
1617
res <- AX.request ResponseFormat.json (AX.defaultRequest { url = "/api", method = Left GET })
17-
liftEffect $ log $ "GET /api response: " <> J.stringify res.response
18+
case res.body of
19+
Left err -> log $ "GET /api response failed to decode: " <> AX.printResponseFormatError err
20+
Right json -> log $ "GET /api response: " <> J.stringify json
1821

19-
main' = launchAff $ do
22+
main' :: Effect Unit
23+
main' = void $ launchAff $ do
2024
res1 <- AX.get ResponseFormat.json "/api"
21-
liftEffect $ log $ "GET /api response: " <> J.stringify res1.response
25+
case res1.body of
26+
Left err -> log $ "GET /api response failed to decode: " <> AX.printResponseFormatError err
27+
Right json -> log $ "GET /api response: " <> J.stringify json
2228

2329
res2 <- AX.post ResponseFormat.json "/api" (RequestBody.json (J.fromString "test"))
24-
liftEffect $ log $ "POST /api response: " <> J.stringify res2.response
30+
case res2.body of
31+
Left err -> log $ "POST /api response failed to decode: " <> AX.printResponseFormatError err
32+
Right json -> log $ "POST /api response: " <> J.stringify json

0 commit comments

Comments
 (0)