diff --git a/test/client.test.ts b/test/client.test.ts
new file mode 100644
index 0000000..3be1806
--- /dev/null
+++ b/test/client.test.ts
@@ -0,0 +1,65 @@
+import { expect, test } from "vitest";
+
+import * as Effect from "effect/Effect";
+import { pipe } from "effect/Function";
+import * as Layer from "effect/Layer";
+
+import * as Adapter from "../src/Adapters/Fetch.js";
+import * as Fetch from "../src/Fetch.js";
+import * as Interceptor from "../src/Interceptor.js";
+import * as Response from "../src/Response.js";
+
+import * as BaseUrl from "../src/Interceptors/Url.js";
+import * as Client from "../src/Client.js";
+
+const base_url = "https://reqres.in/api";
+
+const base_url_interceptor = BaseUrl.Url(base_url);
+
+test("should make client with http method methods", async () => {
+ const interceptors = Interceptor.of(base_url_interceptor);
+
+ const adapter = pipe(
+ Interceptor.make(interceptors),
+ Interceptor.provide(Adapter.fetch),
+ Fetch.effect
+ );
+
+ const result = await pipe(
+ Client.get("/users/2"),
+ Effect.flatMap(Response.json),
+ Effect.provide(adapter),
+ Effect.runPromise
+ );
+
+ expect(result.data.id).toBe(2);
+});
+
+test("should make client with base URL for every request", async () => {
+ const program = Effect.gen(function* () {
+ const client = yield* Client.Client;
+ return yield* Effect.flatMap(client.get("/users/2"), Response.json);
+ });
+
+ const client = Client.create({ url: base_url, adapter: Adapter.fetch });
+ const layer = Layer.effect(Client.Client, client);
+
+ const result = await Effect.runPromise(Effect.provide(program, layer));
+
+ expect(result.data.id).toBe(2);
+});
+
+test("should make client with interceptors", async () => {
+ const program = Effect.gen(function* () {
+ const client = yield* Client.Client;
+ return yield* Effect.flatMap(client.get("/users/2"), Response.json);
+ });
+
+ const interceptors = Interceptor.of(base_url_interceptor);
+ const client = Client.create({ interceptors, adapter: Adapter.fetch });
+ const layer = Layer.effect(Client.Client, client);
+
+ const result = await program.pipe(Effect.provide(layer), Effect.runPromise);
+
+ expect(result.data.id).toBe(2);
+});
diff --git a/test/index.test.ts b/test/index.test.ts
index b795675..2afb4ec 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -1,69 +1,34 @@
-import { describe, expect, test } from "vitest";
+import { expect, test } from "vitest";
import * as Effect from "effect/Effect";
-import * as Either from "effect/Either";
import { pipe } from "effect/Function";
import * as Stream from "effect/Stream";
-import * as Layer from "effect/Layer";
import * as Adapter from "../src/Adapters/Fetch.js";
import * as Fetch from "../src/Fetch.js";
-import * as Interceptor from "../src/Interceptor.js";
-import * as Request from "../src/Request.js";
import * as Response from "../src/Response.js";
import { DecodeError } from "../src/internal/error.js";
-import * as BaseUrl from "../src/Interceptors/Url.js";
-
-import * as Client from "../src/Client.js";
-
-const adapter = Fetch.make(Adapter.fetch);
-
const base_url = "https://reqres.in/api";
-const base_url_interceptor = Effect.flatMap(Interceptor.Context, (_) => {
- const request = _.request.clone();
- const url = request.url.toString();
- const newUrl = base_url.replace(/\/+$/, "") + "/" + url.replace(/^\/+/, "");
- return _.proceed(Request.make(newUrl, request.init));
-});
-
-const pass_through = Effect.flatMap(Interceptor.Context, (_) =>
- _.proceed(_.request)
-);
-
-class Err {
- readonly _tag = "Err";
-}
-
-const error_interceptor = Effect.fail(new Err());
-
const makeClient = (effect: Effect.Effect) => {
- const interceptors = Interceptor.empty().pipe(
- Interceptor.add(base_url_interceptor)
- );
-
- const interceptor = Interceptor.provide(
- Interceptor.make(interceptors),
- Adapter.fetch
- );
-
- return Effect.provide(effect, Fetch.effect(interceptor));
+ const adapter = Fetch.make(Adapter.fetch);
+ return Effect.provide(effect, adapter);
};
test("google", async () => {
- const result = await pipe(
+ const program = Effect.flatMap(
Fetch.fetch("https://www.google.com"),
- Effect.flatMap(Response.text),
- Effect.provide(adapter),
- Effect.runPromise
+ Response.text
);
+ const result = await Effect.runPromise(makeClient(program));
+
expect(result).toContain("Google");
});
test("streaming", async () => {
- const result = await pipe(
+ const program = pipe(
Fetch.fetch("https://www.google.com"),
Effect.map((_) =>
_.body == null
@@ -74,224 +39,31 @@ test("streaming", async () => {
)
),
Stream.unwrap,
- Stream.runFold("", (a, b) => a + new TextDecoder().decode(b)),
- Effect.provide(adapter),
- Effect.runPromise
+ Stream.runFold("", (a, b) => a + new TextDecoder().decode(b))
);
+ const result = await Effect.runPromise(makeClient(program));
+
expect(result).toContain("Google");
});
-test("with client factory", async () => {
- const program = Effect.flatMap(Fetch.fetch("/users/2"), Response.json);
+test("should make request", async () => {
+ const program = Effect.flatMap(
+ Fetch.fetch(base_url + "/users/2"),
+ Response.json
+ );
const result = await Effect.runPromise(makeClient(program));
expect(result.data.id).toBe(2);
});
test("fetch with Effect.gen", async () => {
- const newAdapter = pipe(
- Interceptor.make(Interceptor.of(base_url_interceptor)),
- Effect.provide(adapter),
- Fetch.effect
- );
-
- const program = Effect.gen(function*() {
+ const program = Effect.gen(function* () {
const fetch = yield* Fetch.Fetch;
- const res = yield* fetch("/users/2")
- return yield* Response.json(res)
- })
-
- const result = await program.pipe(
- Effect.provide(newAdapter),
- Effect.runPromise
- );
-
- expect(result.data.id).toBe(2);
-});
-
-describe("Interceptors", () => {
- test("single", async () => {
- const newAdapter = pipe(
- Interceptor.make(Interceptor.of(base_url_interceptor)),
- Effect.provide(adapter),
- Fetch.effect
- );
-
- const result = await pipe(
- Fetch.fetch("/users/2"),
- Effect.flatMap(Response.json),
- Effect.provide(newAdapter),
- Effect.runPromise
- );
-
- expect(result.data.id).toBe(2);
+ const res = yield* fetch(base_url + "/users/2");
+ return yield* Response.json(res);
});
- test("error", async () => {
- const interceptors = pipe(
- Interceptor.empty(),
- Interceptor.add(base_url_interceptor),
- Interceptor.add(error_interceptor)
- );
-
- const newAdapter = pipe(
- Interceptor.make(interceptors),
- Effect.provide(adapter),
- Fetch.effect
- );
-
- const result = await pipe(
- Fetch.fetch("/users/2"),
- Effect.flatMap(Response.json),
- Effect.provide(newAdapter),
- Effect.either,
- Effect.runPromise
- );
-
- expect(Either.isLeft(result)).toBeTruthy();
- expect((result as Either.Left).left).toEqual({ _tag: "Err" });
- });
-
- test("should intercept and change response", async () => {
- const evil_interceptor = Effect.succeed(
- new globalThis.Response(JSON.stringify({ data: { id: "😈 evil" } }))
- );
-
- const interceptors = pipe(
- Interceptor.empty(),
- Interceptor.add(base_url_interceptor),
- Interceptor.add(evil_interceptor)
- );
-
- const adapter = pipe(
- Interceptor.make(interceptors),
- Interceptor.provide(Adapter.fetch),
- Fetch.effect
- );
-
- const result = await pipe(
- Fetch.fetch("/users/2"),
- Effect.flatMap(Response.json),
- Effect.provide(adapter),
- Effect.runPromise
- );
-
- expect(result.data.id).not.toBe(2);
- });
-
- test("should copy interceptors", async () => {
- const explode = Effect.fail({ explosive: "boom" });
-
- const interceptors = Interceptor.add(
- Interceptor.empty(),
- base_url_interceptor
- );
-
- const clone = pipe(
- Interceptor.copy(interceptors),
- Interceptor.add(explode),
- Interceptor.make
- );
-
- const adapter = Fetch.effect(Interceptor.provide(clone, Adapter.fetch));
-
- const result = await pipe(
- Fetch.fetch("/users/2"),
- Effect.provide(adapter),
- Effect.either,
- Effect.runPromise
- );
-
- expect(result).toEqual(Either.left({ explosive: "boom" }));
- });
-
- describe("Base URL", () => {
- test("should attach url to every outgoing request", async () => {
- const interceptors = Interceptor.of(BaseUrl.Url(base_url));
-
- const adapter = Interceptor.make(interceptors).pipe(
- Interceptor.provide(Adapter.fetch)
- );
-
- const result = await pipe(
- Fetch.fetch("/users/2"),
- Effect.flatMap(Response.json),
- Effect.provide(Fetch.effect(adapter)),
- Effect.runPromise
- );
-
- expect(result.data.id).toBe(2);
- });
-
- test("async: should attach url to every outgoing request", async () => {
- const adapter = Fetch.effect(
- Effect.gen(function* () {
- const interceptors = Interceptor.of(BaseUrl.Url(base_url));
- return yield* Interceptor.provide(
- Interceptor.make(interceptors),
- Adapter.fetch
- );
- })
- );
-
- const result = await pipe(
- Fetch.fetch("/users/2"),
- Effect.flatMap(Response.json),
- Effect.provide(adapter),
- Effect.runPromise
- );
-
- expect(result.data.id).toBe(2);
- });
- });
-});
-
-describe("Client", () => {
- test("can access individual client services", async () => {
- const interceptors = Interceptor.of(base_url_interceptor);
-
- const adapter = pipe(
- Interceptor.make(interceptors),
- Interceptor.provide(Adapter.fetch),
- Fetch.effect
- );
-
- const result = await pipe(
- Client.get("/users/2"),
- Effect.flatMap(Response.json),
- Effect.provide(adapter),
- Effect.runPromise
- );
-
- expect(result.data.id).toBe(2);
- });
-
- test("should construct client with base URL", async () => {
- const program = Effect.gen(function* () {
- const client = yield* Client.Client;
- return yield* Effect.flatMap(client.get("/users/2"), Response.json);
- });
-
- const client = Client.create({ url: base_url, adapter: Adapter.fetch });
- const layer = Layer.effect(Client.Client, client);
-
- const result = await program.pipe(Effect.provide(layer), Effect.runPromise);
-
- expect(result.data.id).toBe(2);
- });
-
- test("should construct client with interceptors", async () => {
- const program = Effect.gen(function* () {
- const client = yield* Client.Client;
- return yield* Effect.flatMap(client.get("/users/2"), Response.json);
- });
-
- const interceptors = Interceptor.of(base_url_interceptor);
- const client = Client.create({ interceptors, adapter: Adapter.fetch });
- const layer = Layer.effect(Client.Client, client);
-
- const result = await program.pipe(Effect.provide(layer), Effect.runPromise);
+ const result = await Effect.runPromise(makeClient(program));
- expect(result.data.id).toBe(2);
- });
+ expect(result.data.id).toBe(2);
});
diff --git a/test/interceptor.test.ts b/test/interceptor.test.ts
new file mode 100644
index 0000000..77e1656
--- /dev/null
+++ b/test/interceptor.test.ts
@@ -0,0 +1,158 @@
+import { expect, test } from "vitest";
+
+import * as Chunk from "effect/Chunk";
+import * as Effect from "effect/Effect";
+import * as Either from "effect/Either";
+import { pipe } from "effect/Function";
+
+import * as Adapter from "../src/Adapters/Fetch.js";
+import * as Fetch from "../src/Fetch.js";
+import * as Interceptor from "../src/Interceptor.js";
+import * as Response from "../src/Response.js";
+
+import * as BaseUrl from "../src/Interceptors/Url.js";
+
+const adapter = Fetch.make(Adapter.fetch);
+
+const base_url = "https://reqres.in/api";
+
+const base_url_interceptor = BaseUrl.Url(base_url);
+
+class Err {
+ readonly _tag = "Err";
+}
+
+const error_interceptor = Effect.fail(new Err());
+
+test("should create handler with single interceptor", async () => {
+ const fetchWithInterceptor = pipe(
+ Interceptor.make(Interceptor.of(base_url_interceptor)),
+ Interceptor.provide(Adapter.fetch),
+ Fetch.effect
+ );
+
+ const result = await pipe(
+ Fetch.fetch("/users/2"),
+ Effect.flatMap(Response.json),
+ Effect.provide(fetchWithInterceptor),
+ Effect.runPromise
+ );
+
+ expect(result.data.id).toBe(2);
+});
+
+test("should create handler with early error interceptor", async () => {
+ const interceptors = pipe(
+ Interceptor.empty(),
+ Interceptor.add(base_url_interceptor),
+ Interceptor.add(error_interceptor)
+ );
+
+ const newAdapter = pipe(
+ Interceptor.make(interceptors),
+ Effect.provide(adapter),
+ Fetch.effect
+ );
+
+ const result = await pipe(
+ Fetch.fetch("/users/2"),
+ Effect.flatMap(Response.json),
+ Effect.provide(newAdapter),
+ Effect.either,
+ Effect.runPromise
+ );
+
+ expect(Either.isLeft(result)).toBeTruthy();
+ expect((result as Either.Left).left).toEqual({ _tag: "Err" });
+});
+
+test("should create handler with early success response", async () => {
+ const evil_interceptor = Effect.succeed(
+ new globalThis.Response(JSON.stringify({ data: { id: "😈 evil" } }))
+ );
+
+ const interceptors = pipe(
+ Interceptor.empty(),
+ Interceptor.add(base_url_interceptor),
+ Interceptor.add(evil_interceptor)
+ );
+
+ const adapter = pipe(
+ Interceptor.make(interceptors),
+ Interceptor.provide(Adapter.fetch),
+ Fetch.effect
+ );
+
+ const result = await pipe(
+ Fetch.fetch("/users/2"),
+ Effect.flatMap(Response.json),
+ Effect.provide(adapter),
+ Effect.runPromise
+ );
+
+ expect(result.data.id).not.toBe(2);
+ expect(result.data.id).toBe("😈 evil");
+});
+
+test("should copy/inherit interceptors", async () => {
+ const explode = Effect.fail({ explosive: "boom" });
+
+ const interceptors = Interceptor.add(
+ Interceptor.empty(),
+ base_url_interceptor
+ );
+
+ const clone = Interceptor.add(Interceptor.copy(interceptors), explode);
+
+ const adapter = Fetch.effect(
+ Interceptor.provide(Interceptor.make(clone), Adapter.fetch)
+ );
+
+ const result = await pipe(
+ Fetch.fetch("/users/2"),
+ Effect.provide(adapter),
+ Effect.either,
+ Effect.runPromise
+ );
+
+ expect(Chunk.size(clone)).not.toEqual(Chunk.size(interceptors));
+ expect(result).toEqual(Either.left({ explosive: "boom" }));
+});
+
+test("should attach url to every outgoing request", async () => {
+ const interceptors = Interceptor.of(base_url_interceptor);
+
+ const adapter = Interceptor.make(interceptors).pipe(
+ Interceptor.provide(Adapter.fetch)
+ );
+
+ const result = await pipe(
+ Fetch.fetch("/users/2"),
+ Effect.flatMap(Response.json),
+ Effect.provide(Fetch.effect(adapter)),
+ Effect.runPromise
+ );
+
+ expect(result.data.id).toBe(2);
+});
+
+test("should make interceptor from effect", async () => {
+ const adapter = Fetch.effect(
+ Effect.gen(function* () {
+ const interceptors = Interceptor.of(BaseUrl.Url(base_url));
+ return yield* Interceptor.provide(
+ Interceptor.make(interceptors),
+ Adapter.fetch
+ );
+ })
+ );
+
+ const result = await pipe(
+ Fetch.fetch("/users/2"),
+ Effect.flatMap(Response.json),
+ Effect.provide(adapter),
+ Effect.runPromise
+ );
+
+ expect(result.data.id).toBe(2);
+});