From 2f9287b90f6eb03ca767edadf6e9c4f1e1cddcb1 Mon Sep 17 00:00:00 2001 From: duzda <25201406+duzda@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:16:55 +0200 Subject: [PATCH] Implement AbortSignal, AbortController, RequestInit Does not fully implement RequestInit, only what seemed useful. Closes: #4 Closes: #10 --- .github/workflows/test.yml | 2 +- src/gleam/fetch.gleam | 112 +++++++++++++- src/gleam/fetch/abort_controller.gleam | 54 +++++++ src/gleam/fetch/abort_signal.gleam | 99 +++++++++++++ src/gleam/fetch/fetch_options.gleam | 198 +++++++++++++++++++++++++ src/gleam_fetch_ffi.mjs | 54 ++++++- test/gleam_fetch_test.gleam | 121 +++++++++++++++ test/polyfills.js | 4 +- 8 files changed, 637 insertions(+), 7 deletions(-) create mode 100644 src/gleam/fetch/abort_controller.gleam create mode 100644 src/gleam/fetch/abort_signal.gleam create mode 100644 src/gleam/fetch/fetch_options.gleam diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc189d0..7c31b5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v2 with: - node-version: "19.3.0" + node-version: "22.19.0" - uses: erlef/setup-beam@v1 with: otp-version: false diff --git a/src/gleam/fetch.gleam b/src/gleam/fetch.gleam index 2bf80c0..6c75fb1 100644 --- a/src/gleam/fetch.gleam +++ b/src/gleam/fetch.gleam @@ -1,4 +1,5 @@ import gleam/dynamic.{type Dynamic} +import gleam/fetch/fetch_options.{type FetchOptions} import gleam/fetch/form_data.{type FormData} import gleam/http/request.{type Request} import gleam/http/response.{type Response} @@ -13,7 +14,7 @@ import gleam/javascript/promise.{type Promise} pub type FetchError { /// A network error occured, maybe because user lost network connection, /// because the network took to long to answer, or because the - /// server timed out. + /// server timed out. Also happens when fetch has been aborted. NetworkError(String) /// Fetch is unable to read body, for example when body as already been read /// once. @@ -41,7 +42,27 @@ pub type FetchResponse /// |> fetch.raw_send /// ``` @external(javascript, "../gleam_fetch_ffi.mjs", "raw_send") -pub fn raw_send(a: FetchRequest) -> Promise(Result(FetchResponse, FetchError)) +pub fn raw_send( + request: FetchRequest, +) -> Promise(Result(FetchResponse, FetchError)) + +/// Call directly `fetch` with a `Request` and `FetchOptions`, +/// then convert the result back to Gleam. +/// Let you get back a `FetchResponse` instead of the Gleam +/// `gleam/http/response.Response` data. +/// +/// ```gleam +/// request.new() +/// |> request.set_host("example.com") +/// |> request.set_path("/example") +/// |> fetch.to_fetch_request +/// |> fetch.raw_send_with(fetch_options.new()) +/// ``` +@external(javascript, "../gleam_fetch_ffi.mjs", "raw_send") +pub fn raw_send_with( + request: FetchRequest, + options: FetchOptions, +) -> Promise(Result(FetchResponse, FetchError)) /// Call `fetch` with a Gleam `Request(String)`, and convert the result back /// to Gleam. Use it to send strings or JSON stringified. @@ -69,6 +90,34 @@ pub fn send( }) } +/// Call `fetch` with a Gleam `Request(String)` and `FetchOptions`, +/// then convert the result back /// to Gleam. +/// Use it to send strings or JSON stringified. +/// +/// If you're looking for something more low-level, take a look at +/// [`raw_send_with`](#raw_send_with). +/// +/// ```gleam +/// let my_data = json.object([#("field", "value")]) +/// request.new() +/// |> request.set_host("example.com") +/// |> request.set_path("/example") +/// |> request.set_body(json.to_string(my_data)) +/// |> request.set_header("content-type", "application/json") +/// |> fetch.send_with(fetch_options.new()) +/// ``` +pub fn send_with( + request: Request(String), + options: FetchOptions, +) -> Promise(Result(Response(FetchBody), FetchError)) { + request + |> to_fetch_request + |> raw_send_with(options) + |> promise.try_await(fn(resp) { + promise.resolve(Ok(from_fetch_response(resp))) + }) +} + /// Call `fetch` with a Gleam `Request(FormData)`, and convert the result back /// to Gleam. Request will be sent as a `multipart/form-data`, and should be /// decoded as-is on servers. @@ -97,6 +146,36 @@ pub fn send_form_data( }) } +/// Call `fetch` with a Gleam `Request(FormData)` and `FetchOptions`, +/// then convert the result back to Gleam. +/// Request will be sent as a `multipart/form-data`, and should be +/// decoded as-is on servers. +/// +/// If you're looking for something more low-level, take a look at +/// [`raw_send_with`](#raw_send_with). +/// +/// ```gleam +/// request.new() +/// |> request.set_host("example.com") +/// |> request.set_path("/example") +/// |> request.set_body({ +/// form_data.new() +/// |> form_data.append("key", "value") +/// }) +/// |> fetch.send_form_data_with(fetch_options.new()) +/// ``` +pub fn send_form_data_with( + request: Request(FormData), + options: FetchOptions, +) -> Promise(Result(Response(FetchBody), FetchError)) { + request + |> form_data_to_fetch_request + |> raw_send_with(options) + |> promise.try_await(fn(resp) { + promise.resolve(Ok(from_fetch_response(resp))) + }) +} + /// Call `fetch` with a Gleam `Request(FormData)`, and convert the result back /// to Gleam. Binary will be sent as-is, and you probably want a proper /// content-type added. @@ -110,7 +189,7 @@ pub fn send_form_data( /// |> request.set_path("/example") /// |> request.set_body(<<"data">>) /// |> request.set_header("content-type", "application/octet-stream") -/// |> fetch.send_form_data +/// |> fetch.send_bits /// ``` pub fn send_bits( request: Request(BitArray), @@ -123,6 +202,33 @@ pub fn send_bits( }) } +/// Call `fetch` with a Gleam `Request(FormData)` and `FetchOptions`, +/// then convert the result back to Gleam. Binary will be sent as-is, +/// and you probably want a proper content-type added. +/// +/// If you're looking for something more low-level, take a look at +/// [`raw_send_with`](#raw_send_with). +/// +/// ```gleam +/// request.new() +/// |> request.set_host("example.com") +/// |> request.set_path("/example") +/// |> request.set_body(<<"data">>) +/// |> request.set_header("content-type", "application/octet-stream") +/// |> fetch.send_bits_with(fetch_options.new()) +/// ``` +pub fn send_bits_with( + request: Request(BitArray), + options: FetchOptions, +) -> Promise(Result(Response(FetchBody), FetchError)) { + request + |> bitarray_request_to_fetch_request + |> raw_send_with(options) + |> promise.try_await(fn(resp) { + promise.resolve(Ok(from_fetch_response(resp))) + }) +} + /// Convert a Gleam `Request(String)` to a JavaScript /// [`Request`](https://developer.mozilla.org/docs/Web/API/Request), where /// `body` is a string. diff --git a/src/gleam/fetch/abort_controller.gleam b/src/gleam/fetch/abort_controller.gleam new file mode 100644 index 0000000..f3dc46e --- /dev/null +++ b/src/gleam/fetch/abort_controller.gleam @@ -0,0 +1,54 @@ +import gleam/fetch/abort_signal.{type AbortSignal} + +/// Gleam equivalent of JavaScript [`AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController). +pub type AbortController + +/// `AbortController` allows aborting fetch request using `AbortSignal` with +/// specified reason. +/// +/// The signal has to obtained using `get_controller_signal`, then the fetch can +/// be aborted commonly, either by user action or by timeout. +/// +/// Equivalent to JavaScript [`AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController). +@external(javascript, "../../gleam_fetch_ffi.mjs", "newAbortController") +pub fn new() -> AbortController + +/// Aborts the signal bound to the controller. +/// +/// The default abort reason is "AbortError". +/// +/// ```gleam +/// let controller = abort_controller.new() +/// controller +/// |> abort_controller.abort() +/// ``` +/// +/// Similar to JavaScript [`abort`](https://developer.mozilla.org/docs/Web/API/AbortController/abort). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortControllerAbort") +pub fn abort(abort_controller: AbortController) -> Nil + +/// Aborts the signal bound to the controller with specified reason. +/// +/// ```gleam +/// let controller = abort_controller.new() +/// controller +/// |> abort_controller.abort_with("Cancelled operation") +/// ``` +/// +/// Similar to JavaScript [`abort`](https://developer.mozilla.org/docs/Web/API/AbortController/abort). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortControllerAbort") +pub fn abort_with(abort_controller: AbortController, reason: String) -> Nil + +/// Returns the associated ['AbortSignal'](https://developer.mozilla.org/docs/Web/API/AbortSignal). +/// +/// The signal is commonly then passed to the `FetchOptions`. +/// +/// ```gleam +/// let signal = abort_controller.new().get_controller_signal() +/// let options = fetch_options.new() +/// |> fetch_options.set_signal(signal) +/// ``` +/// +/// Equivalent to JavaScript [`signal`](https://developer.mozilla.org/docs/Web/API/AbortController/signal). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortControllerGetSignal") +pub fn get_controller_signal(abort_controller: AbortController) -> AbortSignal diff --git a/src/gleam/fetch/abort_signal.gleam b/src/gleam/fetch/abort_signal.gleam new file mode 100644 index 0000000..74d8e29 --- /dev/null +++ b/src/gleam/fetch/abort_signal.gleam @@ -0,0 +1,99 @@ +/// Gleam equivalent of JavaScript [`AbortSignal`](https://developer.mozilla.org/docs/Web/API/AbortSignal). +pub type AbortSignal + +/// Returns whether signal has already been aborted. +/// +/// ```gleam +/// let aborted = abort_controller.new() +/// |> abort_controller.get_controller_signal +/// |> abort_signal.get_aborted +/// ``` +/// +/// Equivalent to JavaScript [`aborted`](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortSignalAborted") +pub fn get_aborted(signal: AbortSignal) -> Bool + +/// Returns the specified reason for abortion. +/// +/// Default reason is "AbortError". +/// +/// ```gleam +/// let reason = abort_controller.new() +/// |> abort_controller.get_controller_signal +/// |> abort_signal.get_reason +/// ``` +/// +/// Equivalent to JavaScript [`reason`](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortSignalReason") +pub fn get_reason(signal: AbortSignal) -> String + +/// Creates new signal that is already aborted. +/// +/// The default abort reason is "AbortError". +/// +/// ```gleam +/// let signal = abort_signal.abort() +/// +/// let req = request.new() +/// |> request.set_host("example.com") +/// |> request.set_path("/example") +/// +/// let options = +/// fetch_options.new() +/// |> fetch_options.set_signal(signal) +/// +/// fetch.send_with(req, options) +/// ``` +/// +/// Similar to JavaScript [`abort`](https://developer.mozilla.org/docs/Web/API/AbortController/abort). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortSignalAbort") +pub fn abort() -> AbortSignal + +/// Creates new signal that is already aborted with specified reason. +/// +/// ```gleam +/// let signal = abort_signal.abort_with("Cancelled") +/// +/// let req = request.new() +/// |> request.set_host("example.com") +/// |> request.set_path("/example") +/// +/// let options = +/// fetch_options.new() +/// |> fetch_options.set_signal(signal) +/// +/// fetch.send_with(req, options) +/// ``` +/// +/// Similar to JavaScript [`abort`](https://developer.mozilla.org/docs/Web/API/AbortController/abort). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortSignalAbort") +pub fn abort_with(reason: String) -> AbortSignal + +/// Creates new signal composed of multiple other signals. +/// +/// This is useful, if you want to have a request that can either +/// timeout or be cancelled by the user. +/// +/// ```gleam +/// let signal = +/// abort_controller.new() +/// |> abort_controller.get_controller_signal +/// +/// let multi_signal = abort_signal.from([signal, abort_signal.timeout(500)]) +/// ``` +/// +/// Equivalent to JavaScript ['any'](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortSignalFrom") +pub fn from(signals: List(AbortSignal)) -> AbortSignal + +/// Creates new signal that will error on timeout after specified time. +/// +/// The reason message is "TimeoutError", on unsupported browsers "TypeError". +/// +/// ```gleam +/// let signal = abort_signal.timeout(500) +/// ``` +/// +/// Equivalent to JavaScript ['timeout'](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static). +@external(javascript, "../../gleam_fetch_ffi.mjs", "abortSignalTimeout") +pub fn timeout(time: Int) -> AbortSignal diff --git a/src/gleam/fetch/fetch_options.gleam b/src/gleam/fetch/fetch_options.gleam new file mode 100644 index 0000000..254c477 --- /dev/null +++ b/src/gleam/fetch/fetch_options.gleam @@ -0,0 +1,198 @@ +import gleam/dynamic.{type Dynamic} +import gleam/fetch/abort_signal.{type AbortSignal} + +/// Gleam equivalent of JavaScript [`RequestInit`](https://developer.mozilla.org/docs/Web/API/RequestInit). +pub type FetchOptions + +/// Cache options, for details see [`cache`](https://developer.mozilla.org/docs/Web/API/RequestInit#cache). +pub type Cache { + Default + NoStore + Reload + NoCache + ForceCache + OnlyIfCached +} + +/// Credentials options, for details see [`credentials`](https://developer.mozilla.org/docs/Web/API/RequestInit#credentials). +pub type Credentials { + CredentialsOmit + CredentialsSameOrigin + CredentialsInclude +} + +/// Cors options, for details see [`mode`](https://developer.mozilla.org/docs/Web/API/RequestInit#mode). +pub type Cors { + SameOrigin + Cors + NoCors + Navigate +} + +/// Priority options, for details see [`priority`](https://developer.mozilla.org/docs/Web/API/RequestInit#priority). +pub type Priority { + High + Low + Auto +} + +/// Redirect options, for details see [`redirect`](https://developer.mozilla.org/docs/Web/API/RequestInit#redirect). +pub type Redirect { + Follow + Error + Manual +} + +/// Creates new empty `FetchOptions` object. +/// +/// Useful if more precise control over fetch is required, such as +/// using signals, cache options and so on. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_cache(fetch_options.NoStore) +/// ``` +@external(javascript, "../../gleam_fetch_ffi.mjs", "newFetchOptions") +pub fn new() -> FetchOptions + +/// Sets the [`cache`](https://developer.mozilla.org/docs/Web/API/RequestInit#cache) option of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_cache(fetch_options.NoStore) +/// ``` +pub fn set_cache(fetch_options: FetchOptions, cache: Cache) -> FetchOptions { + set_key( + fetch_options, + "cache", + dynamic.from(case cache { + Default -> "default" + NoStore -> "no-store" + Reload -> "reload" + NoCache -> "no-cache" + ForceCache -> "force-cache" + OnlyIfCached -> "only-if-cached" + }), + ) +} + +/// Sets the [`credentials`](https://developer.mozilla.org/docs/Web/API/RequestInit#credentials) option of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_credentials(fetch_options.CredentialsOmit) +/// ``` +pub fn set_credentials( + fetch_options: FetchOptions, + credentials: Credentials, +) -> FetchOptions { + set_key( + fetch_options, + "credentials", + dynamic.from(case credentials { + CredentialsOmit -> "omit" + CredentialsSameOrigin -> "same-origin" + CredentialsInclude -> "include" + }), + ) +} + +/// Sets the [`keepalive`](https://developer.mozilla.org/docs/Web/API/RequestInit#keepalive) option of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_keepalive(True) +/// ``` +pub fn set_keepalive( + fetch_options: FetchOptions, + keepalive: Bool, +) -> FetchOptions { + set_key(fetch_options, "keepalive", dynamic.from(keepalive)) +} + +/// Sets the [`cors`](https://developer.mozilla.org/docs/Web/API/RequestInit#mode) option of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_cors(fetch_options.SameOrigin) +/// ``` +pub fn set_cors(fetch_options: FetchOptions, cors: Cors) -> FetchOptions { + set_key( + fetch_options, + "mode", + dynamic.from(case cors { + SameOrigin -> "same-origin" + Cors -> "cors" + NoCors -> "no-cors" + Navigate -> "navigate" + }), + ) +} + +/// Sets the [`priority`](https://developer.mozilla.org/docs/Web/API/RequestInit#priority) option of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_cors(fetch_options.High) +/// ``` +pub fn set_priority( + fetch_options: FetchOptions, + priority: Priority, +) -> FetchOptions { + set_key( + fetch_options, + "priority", + dynamic.from(case priority { + High -> "high" + Low -> "low" + Auto -> "auto" + }), + ) +} + +/// Sets the [`redirect`](https://developer.mozilla.org/docs/Web/API/RequestInit#redirect) option of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_redirect(fetch_options.Follow) +/// ``` +pub fn set_redirect( + fetch_options: FetchOptions, + redirect: Redirect, +) -> FetchOptions { + set_key( + fetch_options, + "redirect", + dynamic.from(case redirect { + Follow -> "follow" + Error -> "error" + Manual -> "manual" + }), + ) +} + +/// Sets the [`signal`](https://developer.mozilla.org/docs/Web/API/RequestInit#signal) of `FetchOptions`. +/// +/// ```gleam +/// let options = fetch_options.new() +/// |> fetch_options.set_signal(abort_signal.abort()) +/// ``` +pub fn set_signal( + fetch_options: FetchOptions, + signal: AbortSignal, +) -> FetchOptions { + set_key(fetch_options, "signal", dynamic.from(signal)) +} + +/// Generic function that sets specified option in the `FetchOptions` object. +/// +/// In JavaScript, this object is simply represented as `{}` with no type-checking, +/// so when implementing new features, you should consult +/// [documentation](https://developer.mozilla.org/docs/Web/API/RequestInit) +/// for valid and sensible keys and values. +@external(javascript, "../../gleam_fetch_ffi.mjs", "setKeyFetchOptions") +fn set_key( + fetch_options: FetchOptions, + key: String, + value: Dynamic, +) -> FetchOptions diff --git a/src/gleam_fetch_ffi.mjs b/src/gleam_fetch_ffi.mjs index 0bc8944..7070dc1 100644 --- a/src/gleam_fetch_ffi.mjs +++ b/src/gleam_fetch_ffi.mjs @@ -9,9 +9,9 @@ import { UnableToReadBody, } from "../gleam_fetch/gleam/fetch.mjs"; -export async function raw_send(request) { +export async function raw_send(request, options) { try { - return new Ok(await fetch(request)); + return new Ok(await fetch(request, options)); } catch (error) { return new Error(new NetworkError(error.toString())); } @@ -165,3 +165,53 @@ export function keysFormData(formData) { } return toList([...result]) } + +// FetchOptions functions. + +export function newFetchOptions() { + return {}; +} + +export function setKeyFetchOptions(fetchOptions, key, value) { + fetchOptions[key] = value; + return fetchOptions; +} + +// AbortController functions. + +export function newAbortController() { + return new globalThis.AbortController(); +} + +export function abortControllerAbort(abortController, reason) { + abortController.abort(reason); +} + +export function abortControllerGetSignal(abortController) { + return abortController.signal; +} + +// AbortSignal functions. + +export function abortSignalAborted(abortSignal) { + return abortSignal.aborted; +} + +export function abortSignalReason(abortSignal) { + if (abortSignal.reason.name) { + return abortSignal.reason.name; + } + return abortSignal.reason; +} + +export function abortSignalAbort(reason) { + return globalThis.AbortSignal.abort(reason); +} + +export function abortSignalFrom(abortSignals) { + return globalThis.AbortSignal.any(abortSignals); +} + +export function abortSignalTimeout(timeout) { + return globalThis.AbortSignal.timeout(timeout); +} diff --git a/test/gleam_fetch_test.gleam b/test/gleam_fetch_test.gleam index 68a6d30..6bc51ad 100644 --- a/test/gleam_fetch_test.gleam +++ b/test/gleam_fetch_test.gleam @@ -1,4 +1,7 @@ import gleam/fetch.{type FetchError} +import gleam/fetch/abort_controller +import gleam/fetch/abort_signal +import gleam/fetch/fetch_options import gleam/fetch/form_data import gleam/http.{Get, Head, Options} import gleam/http/request @@ -194,3 +197,121 @@ fn setup_form_data() { |> form_data.append("second-key", "second-value") |> form_data.append_bits("second-key", <<"second-value-bits":utf8>>) } + +pub fn abort_controller_test() { + let controller = abort_controller.new() + controller + |> abort_controller.abort() + + let signal = + controller + |> abort_controller.get_controller_signal() + + let assert True = abort_signal.get_aborted(signal) + let assert "AbortError" = abort_signal.get_reason(signal) +} + +pub fn abort_controller_reason_test() { + let controller = abort_controller.new() + controller + |> abort_controller.abort_with("User error") + + let signal = + controller + |> abort_controller.get_controller_signal() + + let assert True = abort_signal.get_aborted(signal) + let assert "User error" = abort_signal.get_reason(signal) +} + +pub fn abort_one_of_signals_test() { + let signal = + abort_controller.new() + |> abort_controller.get_controller_signal + + let multi_signal = abort_signal.from([signal, abort_signal.abort()]) + + let assert False = abort_signal.get_aborted(signal) + let assert True = abort_signal.get_aborted(multi_signal) + let assert "AbortError" = abort_signal.get_reason(multi_signal) +} + +pub fn abort_one_of_signals_with_reason_test() { + let signal = + abort_controller.new() + |> abort_controller.get_controller_signal + + let multi_signal = + abort_signal.from([signal, abort_signal.abort_with("Failing")]) + + let assert False = abort_signal.get_aborted(signal) + let assert True = abort_signal.get_aborted(multi_signal) + let assert "Failing" = abort_signal.get_reason(multi_signal) +} + +pub fn abort_timeout_signal_test() { + // This should instantly timeout. + let signal = abort_signal.timeout(0) + + let req = + request.new() + |> request.set_host("example.com") + |> request.set_path("/example") + + let options = + fetch_options.new() + |> fetch_options.set_signal(signal) + + use result <- promise.await(fetch.send_with(req, options)) + + let assert Error(_) = result + let assert True = abort_signal.get_aborted(signal) + let assert "TimeoutError" = abort_signal.get_reason(signal) + promise.resolve(Nil) +} + +pub fn abort_fetch_test() { + let req = + request.new() + |> request.set_method(Get) + |> request.set_host("test-api.service.hmrc.gov.uk") + |> request.set_path("/hello/world") + |> request.prepend_header("accept", "application/vnd.hmrc.1.0+json") + + let controller = abort_controller.new() + + let signal = controller |> abort_controller.get_controller_signal + + let options = + fetch_options.new() + |> fetch_options.set_signal(signal) + |> fetch_options.set_cache(fetch_options.NoStore) + + abort_controller.abort(controller) + use result <- promise.await(fetch.send_with(req, options)) + + let assert Error(_) = result + promise.resolve(Nil) +} + +pub fn complex_fetch_options_test() { + let req = + request.new() + |> request.set_method(Get) + |> request.set_host("test-api.service.hmrc.gov.uk") + |> request.set_path("/hello/world") + |> request.prepend_header("accept", "application/vnd.hmrc.1.0+json") + + let options = + fetch_options.new() + |> fetch_options.set_cache(fetch_options.NoStore) + |> fetch_options.set_cors(fetch_options.Cors) + |> fetch_options.set_credentials(fetch_options.CredentialsOmit) + |> fetch_options.set_keepalive(True) + |> fetch_options.set_priority(fetch_options.High) + |> fetch_options.set_redirect(fetch_options.Follow) + + use result <- promise.await(fetch.send_with(req, options)) + let assert Ok(_) = result + promise.resolve(Nil) +} diff --git a/test/polyfills.js b/test/polyfills.js index a71db69..c26d837 100644 --- a/test/polyfills.js +++ b/test/polyfills.js @@ -1,8 +1,10 @@ -import { default as fetch, Headers, Request, Response } from "node-fetch"; +import { default as fetch, Headers, Request, Response, AbortSignal, AbortController } from "node-fetch"; if (!globalThis.fetch) { globalThis.fetch = fetch; globalThis.Headers = Headers; globalThis.Request = Request; globalThis.Response = Response; + globalThis.AbortController = AbortController; + globalThis.AbortSignal = AbortSignal; }