git.iko.soy/rowtype-yoga/purescript-fetch
on main help sign in
Cleanup and polishing

- Add ci.yml - Add documentation - Cleanup

commit4faa6bbaca2942a91647eadb256914f60db4b09d authorsigma-andex <77549848+sigma-andex@users.noreply.github.com> dateFri 19 Aug 2022, 16:42 (4 years ago) parent7dbace4
12 files changed+265−94
.github/workflows/check.yaml+48 README.md+48−1 spago.dhall+1 src/Fetch.purs+86 src/Fetch/Fetch.purs−37 src/Fetch/Internal/Request.purs+10−2 src/Fetch/Internal/RequestBody.purs+1−4 src/Fetch/Internal/Response.purs+11−13 test.dhall+1−1 test/Fetch/FetchSpec.purs−36 test/Test/FetchSpec.js+8 test/Test/FetchSpec.purs+51
.github/workflows/check.yamlnew file+48
@@ -0,0 +1,48 @@
1+name: CI
2+
3+on:
4+ push:
5+ branches: [main]
6+ pull_request:
7+ branches: [main]
8+
9+jobs:
10+ build:
11+ runs-on: ubuntu-latest
12+
13+ steps:
14+ - uses: actions/checkout@v2
15+
16+ - name: Set up a PureScript toolchain
17+ uses: purescript-contrib/setup-purescript@main
18+ with:
19+ purescript: "unstable"
20+ purs-tidy: "latest"
21+
22+ - uses: actions/setup-node@v2
23+ with:
24+ node-version: "18"
25+ - name: Cache PureScript dependencies
26+ uses: actions/cache@v2
27+ with:
28+ key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }}
29+ path: |
30+ .spago
31+ output
32+ - name: Install dependencies
33+ run: spago install
34+
35+ - name: Build source
36+ run: spago build --no-install --purs-args '--censor-lib --strict'
37+
38+ - name: Run tests
39+ run: NODE_OPTIONS=--experimental-fetch spago -x test.dhall test
40+
41+ - name: Check formatting
42+ run: purs-tidy check src test
43+
44+ - name: Verify Bower & Pulp
45+ run: |
46+ npm install bower pulp@16.0.1
47+ npx bower install
48+ npx pulp build -- --censor-lib --strict
README.md+48−1
@@ -2,8 +2,55 @@
22
33High-level library for the [WHATWG Fetch Living Standard](https://fetch.spec.whatwg.org/).
44
5−## Usage
5+**Note:** This library requires Node.js version `>17.5`, see [# Usage](#usage).
6+
7+## Installation
68
79```bash
810spago install fetch
911```
12+
13+## Usage
14+
15+**Note:**
16+Node.js `<17.5` is not supported.
17+Node.js `>=17.5` and `<18.0` requires the `--experimental-fetch` node options:
18+```bash
19+NODE_OPTIONS=--experimental-fetch spago run
20+```
21+Node.js `>18.0` you don't need to set the `--experimental-fetch` node option.
22+
23+Perform a simple `GET` request:
24+```purescript
25+fetch "https://httpbin.org/get" {} >>= _.text
26+```
27+
28+Perform a `POST` request:
29+```purescript
30+do
31+ { status, text } <- fetch "https://httpbin.org/post"
32+ { method: POST
33+ , mode: Cors
34+ , body: """{"hello":"world"}"""
35+ , headers: { "Content-Type": "application/json" }
36+ , referrer: (ReferrerUrl "https://httpbin.org")
37+ }
38+ responseText <- text
39+```
40+
41+### Json parsing
42+
43+`fetch` works well with `yoga-json` and `argonaut`, use our little helper libraries.
44+
45+### yoga-json
46+You can use `fetch-yoga-json` helper library to simplify json handling
47+
48+```purescript
49+do
50+ { json } <- fetch "https://httpbin.org/post"
51+ { method: POST
52+ , body: writeJSON { hello: "world" }
53+ , headers: { "Content-Type": "application/json" }
54+ }
55+ { "data": d, url, origin } <- fromJson json
56+```
spago.dhall+1
@@ -13,6 +13,7 @@
1313 , "typelevel-prelude"
1414 , "unsafe-coerce"
1515 , "web-file"
16+ , "web-promise"
1617 , "web-streams"
1718 ]
1819, packages = ./packages.dhall
src/Fetch.pursnew file+86
@@ -0,0 +1,86 @@
1+module Fetch
2+ ( fetch
3+ , fetchBody
4+ , module Data.HTTP.Method
5+ , module Fetch.Core.RequestCache
6+ , module Fetch.Core.RequestCredentials
7+ , module Fetch.Core.RequestMode
8+ , module Fetch.Core.Referrer
9+ , module Fetch.Core.Integrity
10+ , module Fetch.Internal.Request
11+ , module Fetch.Internal.RequestBody
12+ , module Fetch.Internal.Response
13+ ) where
14+
15+import Prelude
16+
17+import Control.Promise as Promise
18+import Data.HTTP.Method (Method(..))
19+import Effect.Aff (Aff)
20+import Effect.Class (liftEffect)
21+import Fetch.Core as Core
22+import Fetch.Core.Integrity (Integrity(..))
23+import Fetch.Core.Referrer (Referrer(..))
24+import Fetch.Core.Request as CoreRequest
25+import Fetch.Core.RequestCache (RequestCache(..))
26+import Fetch.Core.RequestCredentials (RequestCredentials(Omit, Include))
27+import Fetch.Core.RequestMode (RequestMode(Cors, NoCors, Navigate))
28+import Fetch.Internal.Request (class ToCoreRequestOptions, class ToCoreRequestOptionsConverter, class ToCoreRequestOptionsHelper, HighlevelRequestOptions, convertHelper, convertImpl, new)
29+import Fetch.Internal.Request as Request
30+import Fetch.Internal.RequestBody (class ToRequestBody, toRequestBody)
31+import Fetch.Internal.Response (Response, ResponseR, arrayBuffer, blob, body, json, promiseToPromise, text)
32+import Fetch.Internal.Response as Response
33+import Prim.Row (class Union)
34+import Type.Row.Homogeneous (class Homogeneous)
35+
36+-- | Implementation of `fetch`, see https://developer.mozilla.org/en-US/docs/Web/API/fetch
37+-- | For usage with `String` bodies. For other body types, see `fetchBody`
38+-- |
39+-- | Usage:
40+-- | ```purescript
41+-- | do
42+-- | let requestUrl = "https://httpbin.org/get"
43+-- | { status, text } <- fetch requestUrl { headers: { "Accept": "application/json" }}
44+-- | responseBody <- text
45+-- | ```
46+-- | A more complex example doing a Post request:
47+-- | ```purescript
48+-- | do
49+-- | { status, json } <- fetch "https://httpbin.org/post"
50+-- | { method: POST
51+-- | , mode: RequestMode.Cors
52+-- | , body: """{"hello":"world"}"""
53+-- | , headers: { "Content-Type": "application/json" }
54+-- | , referrer: ReferrerUrl "https://httpbin.org"
55+-- | }
56+-- | foreignJsonValue <- json
57+-- | ```
58+fetch
59+ :: forall input output thruIn thruOut headers
60+ . Homogeneous headers String
61+ => Union input thruIn (HighlevelRequestOptions headers String)
62+ => Union output thruOut CoreRequest.UnsafeRequestOptions
63+ => ToCoreRequestOptions input output
64+ => String
65+ -> { | input }
66+ -> Aff Response
67+fetch url r = do
68+ request <- liftEffect $ new url $ Request.convert r
69+ cResponse <- Promise.toAffE $ Response.promiseToPromise <$> Core.fetch request
70+ pure $ Response.convert cResponse
71+
72+-- | Like `fetch`, but can accept arbitrary `RequestBody`s.
73+fetchBody
74+ :: forall input output thruIn thruOut headers body
75+ . Homogeneous headers String
76+ => ToRequestBody body
77+ => Union input thruIn (HighlevelRequestOptions headers body)
78+ => Union output thruOut CoreRequest.UnsafeRequestOptions
79+ => ToCoreRequestOptions input output
80+ => String
81+ -> { | input }
82+ -> Aff Response
83+fetchBody url r = do
84+ request <- liftEffect $ new url $ Request.convert r
85+ cResponse <- Promise.toAffE $ Response.promiseToPromise <$> Core.fetch request
86+ pure $ Response.convert cResponse
src/Fetch/Fetch.pursdeleted−37
@@ -1,37 +0,0 @@
1−module Fetch.Fetch
2− ( fetch
3− ) where
4−
5−import Prelude
6−
7−import Control.Promise as Promise
8−import Effect.Aff (Aff)
9−import Effect.Class (liftEffect)
10−import Fetch.Core as Core
11−import Fetch.Core.Request as CoreRequest
12−import Fetch.Internal.Request (class ToCoreRequestOptions, HighlevelRequestOptions, new)
13−import Fetch.Internal.Request as Request
14−import Fetch.Internal.RequestBody (class ToRequestBody)
15−import Fetch.Internal.Response (Response)
16−import Fetch.Internal.Response as Response
17−import Prim.Row (class Union)
18−import Type.Row.Homogeneous (class Homogeneous)
19−import Unsafe.Coerce (unsafeCoerce)
20−
21−-- | Fetch
22−-- |
23−fetch
24− :: forall input output thruIn thruOut headers body
25− . Homogeneous headers String
26− => ToRequestBody body
27− => Union input thruIn (HighlevelRequestOptions headers body)
28− => Union output thruOut CoreRequest.UnsafeRequestOptions
29− => ToCoreRequestOptions input output
30− => String
31− -> { | input }
32− -> Aff Response
33−fetch url r = do
34− request <- liftEffect $ new url $ Request.convert r
35− cResponse <- Promise.toAffE $ unsafeCoerce $ Core.fetch request
36− pure $ Response.convert cResponse
37−
src/Fetch/Internal/Request.purs+10−2
@@ -1,4 +1,13 @@
1−module Fetch.Internal.Request where
1+module Fetch.Internal.Request
2+ ( HighlevelRequestOptions
3+ , class ToCoreRequestOptions
4+ , class ToCoreRequestOptionsConverter
5+ , class ToCoreRequestOptionsHelper
6+ , new
7+ , convert
8+ , convertImpl
9+ , convertHelper
10+ ) where
211
312import Prelude
413
@@ -49,7 +58,6 @@ type HighlevelRequestOptions headers body =
4958 , integrity :: CoreIntegrity.Integrity
5059 )
5160
52−
5361new
5462 :: forall input thru
5563 . Union input thru CoreRequest.UnsafeRequestOptions
src/Fetch/Internal/RequestBody.purs+1−4
@@ -1,7 +1,4 @@
1−module Fetch.Internal.RequestBody
2− (
3− )
4− where
1+module Fetch.Internal.RequestBody where
52
63import Prelude
74
src/Fetch/Internal/Response.purs+11−13
@@ -1,11 +1,20 @@
1−module Fetch.Internal.Response where
1+module Fetch.Internal.Response
2+ ( Response
3+ , ResponseR
4+ , arrayBuffer
5+ , blob
6+ , body
7+ , convert
8+ , json
9+ , promiseToPromise
10+ , text
11+ ) where
212
313import Prelude
414
515import Control.Promise as Control
616import Control.Promise as Promise
717import Data.ArrayBuffer.Types (ArrayBuffer, Uint8Array)
8−import Data.Newtype (class Newtype)
918import Effect (Effect)
1019import Effect.Aff (Aff)
1120import Fetch.Core.Headers (Headers)
@@ -34,17 +43,6 @@ type Response =
3443 { | ResponseR
3544 }
3645
37−data BlobDriver :: forall k. k -> Type
38−data BlobDriver blob = BlobDriver
39−
40−data StreamDriver :: forall k. k -> Type
41−data StreamDriver stream = StreamDriver
42−
43−newtype JsonDriver :: Type -> Type
44−newtype JsonDriver json = JsonDriver (Foreign -> Aff json)
45−
46−derive instance Newtype (JsonDriver json) _
47−
4846text :: CoreResponse.Response -> Aff String
4947text response = CoreResponse.text response <#> promiseToPromise # Promise.toAffE
5048
test.dhall+1−1
@@ -16,6 +16,6 @@ in conf
1616 , "strings"
1717 , "transformers"
1818 , "debug"
19− , "yoga-json"
19+ , "media-types"
2020 ]
2121 }
test/Fetch/FetchSpec.pursdeleted−36
@@ -1,36 +0,0 @@
1−module Test.Fetch.FetchSpec where
2−
3−import Prelude
4−
5−import Data.HTTP.Method (Method(..))
6−import Fetch.Core.Referrer (Referrer(..))
7−import Fetch.Core.RequestMode as RequestMode
8−import Fetch.Fetch (fetch)
9−import Test.Spec (Spec, describe, it)
10−import Test.Spec.Assertions (shouldEqual)
11−
12−spec :: Spec Unit
13−spec =
14− describe "Fetch.Fetch" do
15− describe "fetch" do
16− it "should send a Get request" do
17−
18− response@{ status, json, text } <- fetch "https://httpbin.org/get"
19− { method: GET
20− , headers:
21− { "Content-Type": "application/json"
22− }
23− }
24− status `shouldEqual` 200
25− it "should send a Post request" do
26−
27− response@{ status, json, text } <- fetch "https://httpbin.org/post"
28− { method: POST
29− , mode: RequestMode.Cors
30− , body: """{"hello":"world"}"""
31− , headers:
32− { "Content-Type": "application/json"
33− }
34− , referrer: (ReferrerUrl "https://httpbin.org")
35− }
36− status `shouldEqual` 200
test/Test/FetchSpec.jsnew file+8
@@ -0,0 +1,8 @@
1+
2+import * as stream from 'node:stream'
3+
4+const nodeStream = new stream.Readable()
5+nodeStream.push(`{"hello":"world"}`)
6+nodeStream.push(null)
7+
8+export const helloWorldStream = stream.Readable.toWeb(nodeStream)
test/Test/FetchSpec.pursnew file+51
@@ -0,0 +1,51 @@
1+module Test.FetchSpec where
2+
3+import Prelude
4+
5+import Data.ArrayBuffer.Types (Uint8Array)
6+import Fetch (Method(..), Referrer(..), RequestMode(..), fetch, fetchBody)
7+import Foreign (unsafeFromForeign)
8+import Test.Spec (Spec, describe, it)
9+import Test.Spec.Assertions (shouldEqual)
10+import Web.Streams.ReadableStream (ReadableStream)
11+
12+-- | Returns a new ReadableStream that allows the content of the Blob to be read.
13+foreign import helloWorldStream :: ReadableStream Uint8Array
14+
15+spec :: Spec Unit
16+spec =
17+ describe "Fetch" do
18+ describe "fetch" do
19+ it "should send a Get request" do
20+ let requestUrl = "https://httpbin.org/get"
21+ { status, json } <- fetch requestUrl
22+ { headers: { "Content-Type": "application/json" }
23+ }
24+ { url } <- json <#> unsafeFromForeign
25+ url `shouldEqual` requestUrl
26+ status `shouldEqual` 200
27+
28+ it "should send a Post request" do
29+ { status, json } <- fetch "https://httpbin.org/post"
30+ { method: POST
31+ , mode: Cors
32+ , body: """{"hello":"world"}"""
33+ , headers: { "Content-Type": "application/json" }
34+ , referrer: (ReferrerUrl "https://httpbin.org")
35+ }
36+ { json: j } <- json <#> unsafeFromForeign
37+ j `shouldEqual` { hello: "world" }
38+ status `shouldEqual` 200
39+
40+ it "should send a Post request with stream body" do
41+
42+ { status, json } <- fetchBody "https://httpbin.org/post"
43+ { method: POST
44+ , mode: Cors
45+ , body: helloWorldStream
46+ , headers: { "Content-Type": "application/json" }
47+ , referrer: (ReferrerUrl "https://httpbin.org")
48+ }
49+ { json: j } <- json <#> unsafeFromForeign
50+ j `shouldEqual` { hello: "world" }
51+ status `shouldEqual` 200