Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-chairs-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/platform": patch
---

Allow `HttpClient.followRedirects` to take effect on `FetchHttpClient` by exposing `redirect` through the requestInit FiberRef and documenting the interaction.
5 changes: 5 additions & 0 deletions packages/platform/src/FetchHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import * as internal from "./internal/fetchHttpClient.js"
export class Fetch extends Context.Tag(internal.fetchTagKey)<Fetch, typeof globalThis.fetch>() {}

/**
* The `RequestInit` options provided to `fetch`.
*
* Set `redirect` to `"manual"` when using `HttpClient.followRedirects`, so that
* fetch does not follow redirects before the client decorator can observe them.
*
* @since 1.0.0
* @category tags
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/platform/src/HttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,10 @@ export const withCookiesRef: {
/**
* Follows HTTP redirects up to a specified number of times.
*
* When used with `FetchHttpClient`, provide `FetchHttpClient.RequestInit` with
* `{ redirect: "manual" }` so that `fetch` exposes redirect responses to this
* decorator instead of following them internally.
*
* @since 1.0.0
* @category redirects
*/
Expand Down
25 changes: 21 additions & 4 deletions packages/platform/test/HttpClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,16 +295,33 @@ describe("HttpClient", () => {

it.effect("followRedirects", () =>
Effect.gen(function*() {
const requests: Array<{ readonly url: string; readonly init: RequestInit | undefined }> = []
const fetch: typeof globalThis.fetch = (input, init) => {
const url = typeof input === "string" || input instanceof URL ? input.toString() : input.url
requests.push({ url, init })
if (requests.length === 1) {
return Promise.resolve(new Response(null, {
status: 302,
headers: { location: "/redirected" }
}))
}
return Promise.resolve(new Response("ok"))
}
const defaultClient = yield* HttpClient.HttpClient
const client = defaultClient.pipe(HttpClient.followRedirects())

const response = yield* client.get("https://google.com/")
strictEqual(response.request.url, "https://www.google.com/")
const response = yield* client.get("https://example.com/")
strictEqual(response.request.url, "https://example.com/redirected")
strictEqual(requests.length, 2)
strictEqual(requests[0]!.url, "https://example.com/")
strictEqual(requests[0]!.init?.redirect, "manual")
strictEqual(requests[1]!.url, "https://example.com/redirected")
strictEqual(requests[1]!.init?.redirect, "manual")
}).pipe(
it.flakyTest,
Effect.provide(FetchHttpClient.layer),
Effect.provideService(FetchHttpClient.Fetch, fetch),
Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" })
), 30000)
))

describe("retryTransient", () => {
const makeTestClient = (status: number) => {
Expand Down
Loading